home *** CD-ROM | disk | FTP | other *** search
/ The Atari Compendium / The Atari Compendium (Toad Computers) (1994).iso / files / prgtools / gnustuff / tos / othergnu / texinf~1.zoo / texinfo.st / info / info.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-04-16  |  119.1 KB  |  5,218 lines

  1. /* info -- a stand-alone Info program.
  2.  
  3.    Copyright (C) 1987, 1991 Free Software Foundation, Inc.
  4.  
  5.    This file is part of GNU Info.
  6.  
  7.    GNU Info is distributed in the hope that it will be useful,
  8.    but WITHOUT ANY WARRANTY.  No author or distributor accepts
  9.    responsibility to anyone for the consequences of using it or for
  10.    whether it serves any particular purpose or works at all, unless he
  11.    says so in writing.  Refer to the GNU Emacs General Public License
  12.    for full details.
  13.  
  14.    Everyone is granted permission to copy, modify and redistribute
  15.    GNU Info, but only under the conditions described in the GNU Emacs
  16.    General Public License.   A copy of this license is supposed to
  17.    have been given to you along with GNU Emacs so you can know your
  18.    rights and responsibilities.  It should be in a file named COPYING.
  19.    Among other things, the copyright notice and this notice must be
  20.    preserved on all copies.  */
  21.  
  22. /* This is GNU Info:
  23.  
  24.    Version 1.45  (Change "major_version" and "minor_version" below.)
  25.    Fri Feb  7 1992
  26. */
  27.  
  28. #include <stdio.h>
  29. #include <sys/types.h>
  30. #include <sys/stat.h>
  31. #include <signal.h>
  32. #include <pwd.h>
  33. #include <errno.h>
  34.  
  35. #if !defined (errno)
  36. extern int errno;
  37. #endif /* !errno */
  38.  
  39. #include <ctype.h>
  40. #if __STDC__
  41. #include <stdarg.h>
  42. #endif
  43. #include "getopt.h"
  44.  
  45. #if defined (hpux)
  46. #  define USG
  47. #endif /* hpux */
  48.  
  49. #if defined (USG)
  50.    struct passwd *getpwnam ();
  51. #  include <fcntl.h>
  52. #  include <termio.h>
  53. #  include <string.h>
  54.  
  55. #  if defined (USGr3)
  56. #    if !defined (M_XENIX)
  57. #      include <sys/stream.h>
  58. #      include <sys/ptem.h>
  59. #      undef TIOCGETC
  60. #    else /* M_XENIX */
  61. #      define tchars tc
  62. #      include <sys/ttold.h>
  63. #    endif /* M_XENIX */
  64. #  endif /* USGr3 */
  65. #  define bcopy(source, dest, count) memcpy(dest, source, count)
  66.    char *index(s,c) char *s; { char *strchr(); return strchr(s,c); }
  67.    char *rindex(s,c) char *s; { char *strrchr(); return strrchr(s,c); }
  68. #else /* !USG */
  69. #  include <sys/file.h>
  70. #  include <sgtty.h>
  71. #  include <strings.h>
  72. #endif /* USG */
  73.  
  74. #ifdef atarist
  75. #include <stdlib.h>
  76. #include <termcap.h>
  77. extern int unlink __PROTO((const char *));
  78. extern void sleep __PROTO((unsigned int));
  79. extern char *getwd __PROTO((char *));
  80. extern int kill __PROTO((int, int));
  81. extern int getpid __PROTO((void));
  82. extern void dos2unx __PROTO((const char *, char *unam));
  83. #define STDERR stdout
  84. #define RMODE "rb"
  85. #else
  86. #define STDERR stderr
  87. #define RMODE "r"
  88. char *getenv ();
  89. #endif
  90.  
  91. #ifdef NeXT
  92. #include <libc.h>
  93. extern int tgetent(char *, char *); 
  94. extern int tputs(char *, int , int (*)());
  95. #endif
  96.  
  97. #if !defined (DEFAULT_INFOPATH)
  98. #  define DEFAULT_INFOPATH \
  99.     ".:/usr/gnu/info:/usr/local/emacs/info:/usr/local/lib/emacs/info"
  100. #endif /* !DEFAULT_INFOPATH */
  101.  
  102. typedef struct nodeinfo {
  103.   char *filename;
  104.   char *nodename;
  105.   size_t pagetop;
  106.   size_t nodetop;
  107.   struct nodeinfo *next;
  108. } NODEINFO;
  109.  
  110. typedef struct indirectinfo {
  111.   char *filename;
  112.   size_t first_byte;
  113. } INDIRECT_INFO;
  114.  
  115. typedef int Function ();
  116. #define VOID_SIGHANDLER
  117. #if defined (VOID_SIGHANDLER)
  118. #  define SigHandler void
  119. #else
  120. #  define SigHandler int
  121. #endif /* !VOID_SIGHANDLER */
  122. #define barf(msg) fprintf(stderr, "%s\n", msg)
  123.  
  124. /* Some character stuff. */
  125. #define control_character_threshold 0x020 /* smaller than this is control */
  126. #define meta_character_threshold 0x07f    /* larger than this is Meta. */
  127. #define control_character_bit 0x40    /* 0x000000, must be off. */
  128. #define meta_character_bit 0x080    /* x0000000, must be on. */
  129.  
  130. #define info_separator_char '\037'
  131. #define start_of_node_string "\037"
  132.  
  133. #ifdef CTRL
  134. #undef CTRL
  135. #endif
  136.  
  137. #ifdef META
  138. #undef META
  139. #endif
  140.  
  141. #define CTRL(c) ((c) & (~control_character_bit))
  142. #define META(c) ((c) | meta_character_bit)
  143.  
  144. #define UNMETA(c) ((c) & (~meta_character_bit))
  145. #define UNCTRL(c) to_upper(((c)|control_character_bit))
  146.  
  147. #ifndef to_upper
  148. #define to_upper(c) (((c) < 'a' || (c) > 'z') ? (c) : (c) - 32)
  149. #define to_lower(c) (((c) < 'A' || (c) > 'Z') ? (c) : (c) + 32)
  150. #endif
  151.  
  152. #define CTRL_P(c) ((unsigned char) (c) < control_character_threshold)
  153. #define META_P(c) ((unsigned char) (c) > meta_character_threshold)
  154.  
  155. #define NEWLINE '\n'
  156. #define RETURN CTRL('M')
  157. #define DELETE 0x07f
  158. #define TAB '\t'
  159. #define ABORT_CHAR CTRL('G')
  160. #define PAGE CTRL('L')
  161. #define SPACE 0x020
  162. #define ESC CTRL('[')
  163. #define control_display_prefix '^'
  164.  
  165. #define TAG_TABLE_END_STRING "\037\nEND TAG TABLE"
  166. #define TAG_TABLE_BEG_STRING "\nTAG TABLE:\n"
  167. #define NODE_ID "Node:"
  168. #define NNODENAME 4        /* Default amount to grow nodename list by. */
  169. #define FILENAME_LEN 256
  170. #define NODENAME_LEN 256
  171. #define STRING_SIZE 256
  172. #define nodeend_sequence "\n\037"
  173.  
  174. #define whitespace(c) ((c) == ' ' || (c) == '\t')
  175. #define cr_whitespace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
  176.  
  177. /* All right, some windows stuff. */
  178.  
  179. typedef struct {
  180.   /* Absolute x and y coordinates for usable portion of this window. */
  181.   int left, top, right, bottom;
  182.   /* Absolute cursor position in this window. */
  183.   int ch, cv;
  184. } WINDOW;
  185.  
  186. typedef struct _wind_list {
  187.   int left, top, right, bottom;
  188.   int ch, cv;
  189.   struct _wind_list *next_window;
  190. } WINDOW_LIST;
  191.  
  192. WINDOW the_window = {0, 0, 80, 24, 0, 0};
  193. WINDOW_LIST *window_stack = (WINDOW_LIST *)NULL;
  194. WINDOW terminal_window = {0, 0, 80, 24, 0, 0};
  195.  
  196. /* Not really extern, but defined later in this file. */
  197. extern WINDOW echo_area;
  198. /**
  199. void *xmalloc (), *xrealloc ();
  200. SigHandler info_signal_handler ();
  201. char *getenv (), *next_info_file (), *opsys_filename ();
  202. int build_menu (), find_menu_node ();
  203. void swap_filestack (), pop_filestack ();
  204. **/
  205.  
  206. /* A crock, this should be done in a different way. */
  207. #define MAX_INDIRECT_FILES 100 
  208.  
  209. /* The info history list. */
  210. NODEINFO *Info_History = NULL;
  211.  
  212. /* ?Can't have more than xx files in the indirect list? */
  213. INDIRECT_INFO indirect_list[MAX_INDIRECT_FILES];
  214.  
  215. /* The filename of the currently loaded info file. */
  216. char current_info_file[FILENAME_LEN];
  217.  
  218. /* The nodename of the node the user is looking at. */
  219. char current_info_node[NODENAME_LEN];
  220.  
  221. /* The last file actually loaded.  Not the same as current info file. */
  222. char last_loaded_info_file[FILENAME_LEN];
  223.  
  224. /* Offsets in info_file of top and bottom of current_info_node. */
  225. size_t nodetop, nodebot;
  226.  
  227. /* Number of lines in this node. */
  228. int nodelines;
  229.  
  230. /* Buffer for the info file. */
  231. char *info_file = NULL;
  232.  
  233. /* Length of the above buffer. */
  234. size_t info_buffer_len;
  235.  
  236. /* Pointer to the start of a tag table, or NULL to show none. */
  237. char *tag_table = NULL;
  238.  
  239. /* Length of the above buffer. */
  240. size_t tag_buffer_len;        
  241.  
  242. /* Non-zero means that the tag table is indirect. */
  243. int indirect = 0;
  244. size_t indirect_top;
  245.  
  246. /* Offset in the buffer of the current pagetop. */
  247. size_t pagetop;
  248.  
  249. /* Offset in the buffer of the last displayed character. */
  250. size_t pagebot = 0;
  251.  
  252. /* If non-NULL, this is a colon separated list of directories to search
  253.    for a specific info file.  The user places this variable into his or
  254.    her environment. */
  255. char *infopath = NULL;
  256.  
  257. /* If filled, the name of a file to write to. */
  258. char dumpfile[FILENAME_LEN] = "";
  259.  
  260. /* This is the command to print a node. A default value is compiled in,
  261.    or it can be found from the environment as $INFO_PRINT_COMMAND. */
  262. char *print_command;
  263.  
  264. /* Non-zero means forst redisplay before prompt for the next command. */
  265. int window_bashed = 0;
  266.  
  267. typedef struct completion_entry
  268. {
  269.   char *identifier;
  270.   char *data;
  271.   struct completion_entry *next;
  272. }                COMP_ENTRY;
  273.  
  274. typedef struct {
  275.   char type;
  276.   const char *name;
  277.   char *value;
  278. } termcap_capability_struct;
  279.  
  280. #ifndef __PROTO
  281. #if defined(__STDC__) || defined(__cplusplus)
  282. # define __PROTO(s) s
  283. #else
  284. # define __PROTO(s) ()
  285. #endif
  286. #endif
  287.  
  288. int main __PROTO((int argc, char **argv));
  289. void usage __PROTO((void));
  290. void show_version_info __PROTO((FILE *stream));
  291. void begin_info_session __PROTO((void));
  292. void before_stop_signal __PROTO((void));
  293. void after_stop_signal __PROTO((void));
  294. SigHandler info_signal_handler __PROTO((int sig));
  295. void install_signals __PROTO((void));
  296. void adjust_wind __PROTO((WINDOW *wind, int delta_width, int delta_height));
  297. void free_completion_list __PROTO((void));
  298. void add_completion __PROTO((char *identifier, char *data));
  299. int readline __PROTO((const char *prompt, char *line,
  300.               int maxchars, int completing));
  301. void init_completer __PROTO((void));
  302. COMP_ENTRY *reverse_list __PROTO((COMP_ENTRY *list));
  303. void remember_completion __PROTO((COMP_ENTRY *pointer));
  304. int complete __PROTO((char *text, COMP_ENTRY *list));
  305. int try_complete __PROTO((char *text));
  306. void help_possible_completions __PROTO((char *text));
  307. char *next_info_file __PROTO((char *file, int rewind));
  308. int get_info_file __PROTO((char *filename, int remember_name));
  309. int get_node __PROTO((char *filename, char *nodename, int popping));
  310. void get_node_extent __PROTO((void));
  311. size_t find_node_start __PROTO((size_t start));
  312. size_t find_node_in_tag_table __PROTO((char *nodename, size_t offset));
  313. size_t find_node_in_file __PROTO((char *nodename, size_t offset));
  314. char *make_temp_filename __PROTO((char *starter));
  315. int deletefile __PROTO((char *filename));
  316. int printfile __PROTO((char *filename));
  317. int dump_current_node __PROTO((char *filename));
  318. void toploop __PROTO((void));
  319. int line_length __PROTO((size_t start, size_t end));
  320. void help_use_info __PROTO((void));
  321. int next_node __PROTO((void));
  322. int prev_node __PROTO((void));
  323. int up_node __PROTO((void));
  324. int build_menu __PROTO((void));
  325. int get_menu __PROTO((int item));
  326. int find_menu_node __PROTO((char *string, char *nodename));
  327. int scan_list __PROTO((char *string, char *nodename));
  328. void clean_up __PROTO((char *string));
  329. size_t find_footnote_ref __PROTO((size_t from));
  330. int build_notes __PROTO((void));
  331. int find_note_node __PROTO((char *string, char *nodename));
  332. void display_page __PROTO((void));
  333. void generic_page_display __PROTO((void));
  334. void display_carefully __PROTO((int character));
  335. int cursor_to __PROTO((size_t position));
  336. int next_page __PROTO((void));
  337. int prev_page __PROTO((void));
  338. void set_search_constraints __PROTO((char *buffer, size_t extent));
  339. size_t to_beg_line __PROTO((size_t from));
  340. size_t to_end_line __PROTO((size_t from));
  341. size_t back_lines __PROTO((size_t count, size_t starting_pos));
  342. size_t forward_lines __PROTO((size_t count, size_t starting_pos));
  343. size_t search_forward __PROTO((const char *string, size_t starting_pos));
  344. size_t search_backward __PROTO((const char *string, size_t starting_pos));
  345. size_t string_in_line __PROTO((const char *string, size_t pointer));
  346. size_t skip_whitespace __PROTO((size_t offset));
  347. size_t skip_whitespace_and_cr __PROTO((size_t offset));
  348. int extract_field __PROTO((const char *field_name,
  349.                char *nodename, size_t offset));
  350. int looking_at __PROTO((const char *string, size_t pointer));
  351. int push_filestack __PROTO((char *filename, int remember_name));
  352. void pop_filestack __PROTO((void));
  353. void swap_filestack __PROTO((void));
  354. int push_node __PROTO((char *filename, char *nodename,
  355.                size_t page_position, size_t node_position));
  356. int pop_node __PROTO((char *filename, char *nodename,
  357.               size_t *nodetop, size_t *pagetop));
  358. int strnicmp __PROTO((const char *string1, const char *string2, size_t count));
  359. int stricmp __PROTO((const char *string1, const char *string2));
  360. int get_y_or_n_p __PROTO((void));
  361. void indent_to __PROTO((int screen_column));
  362. void file_error __PROTO((char *file));
  363. void display_error __PROTO((const char *format_string, ...));
  364. int brians_error __PROTO((void));
  365. int open_terminal_io __PROTO((void));
  366. int get_terminal_info __PROTO((void));
  367. #ifdef VERBOSE_GET_TERMINAL
  368. int readline_termcap __PROTO((FILE *stream, char *buffer));
  369. #endif /* VERBOSE_GET_TERMINAL */
  370. int get_terminal_vars __PROTO((termcap_capability_struct from_array[]));
  371. int get_terminal_rows __PROTO((void));
  372. int get_terminal_columns __PROTO((void));
  373. #ifdef TERMINAL_INFO_PRINTING
  374. void show_terminal_info __PROTO((termcap_capability_struct from_array[]));
  375. void tc_pretty_print __PROTO((char *string));
  376. #endif /* TERMINAL_INFO_PRINTING */
  377. void init_terminal_io __PROTO((void));
  378. void ding __PROTO((void));
  379. int blink_cursor __PROTO((void));
  380. void charout __PROTO((int character));
  381. void advance __PROTO((int amount));
  382. void print_string __PROTO((const char *string, ...));
  383. void print_cr __PROTO((void));
  384. void print_tab __PROTO((void));
  385. int display_width __PROTO((int character, int hpos));
  386. void I_goto_xy __PROTO((int xpos, int ypos));
  387. void goto_xy __PROTO((int xpos, int ypos));
  388. void clear_screen __PROTO((void));
  389. void clear_eop_slowly __PROTO((void));
  390. void clear_eop __PROTO((void));
  391. void clear_eol __PROTO((void));
  392. int with_output_to_window __PROTO((WINDOW *window, Function *function));
  393. void set_window __PROTO((WINDOW *window));
  394. void push_window __PROTO((void));
  395. void pop_window __PROTO((void));
  396. void opsys_init_terminal __PROTO((void));
  397. void restore_io __PROTO((void));
  398. void opsys_goto_pos __PROTO((int xpos, int ypos));
  399. int character_output_function __PROTO((int character));
  400. void do_term __PROTO((char *command));
  401. #ifndef atarist
  402. char *find_opsys_filename_result __PROTO((char *caller));
  403. void add_caller_and_result __PROTO((char *caller, char *result));
  404. int opsys_info_file_in_path __PROTO((char *filename, char *path,
  405.                      char *return_string));
  406. char *extract_colon_unit __PROTO((char *string, int *idx));
  407. #endif /* !atarist */
  408. char *opsys_filename __PROTO((char *partial));
  409. void init_echo_area __PROTO((int left, int top, int right, int bottom));
  410. void new_echo_area __PROTO((void));
  411. void close_echo_area __PROTO((void));
  412. void clear_echo_area __PROTO((void));
  413. void make_modeline __PROTO((void));
  414. void open_typeout __PROTO((void));
  415. void close_typeout __PROTO((void));
  416. void *xrealloc __PROTO((void *pointer, size_t bytes));
  417. void *xmalloc __PROTO((size_t bytes));
  418.  
  419.  
  420. /* **************************************************************** */
  421. /*                                    */
  422. /*            Getting Started.                */
  423. /*                                    */
  424. /* **************************************************************** */
  425.  
  426. /* Begin the Info session. */
  427.  
  428. /* Global is on until we are out of trouble. */
  429. int totally_inhibit_errors = 1;
  430.  
  431. /* Non-zero means print version info only. */
  432. int version_flag = 0;
  433.  
  434. /* The major and minor versions of Info. */
  435. int major_version = 1;
  436. int minor_version = 45;
  437.  
  438. struct option long_options[] = {
  439.   { "directory", 1, 0, 'd' },
  440.   { "node", 1, 0, 'n' },
  441.   { "file", 1, 0, 'f' },
  442.   { "output", 1, 0, 'o' },
  443.   { "version", 0, &version_flag, 1 },
  444.   {NULL, 0, NULL, 0}
  445. };
  446.  
  447. #define savestring(x) (char *) strcpy ((char *) xmalloc (1 + strlen (x)), (x))
  448.  
  449. #ifdef atarist
  450. /*
  451.  * Provide declarations and dummy definitions of some
  452.  * signal handling functions which are not in libraries.
  453.  * Don't move that up!  Nasty executable statements inside.
  454.  */
  455. #include "fakemint.h"
  456. #endif /* atarist */
  457.  
  458. #undef __PROTO
  459.  
  460. int
  461. main (argc, argv)
  462.      int argc;
  463.      char **argv;
  464. {
  465.   int c, ind, no_such_node = 0;
  466.   char filename[FILENAME_LEN];
  467.   char *nodename;
  468.   char **nodenames;
  469.   int nodenames_size, nodenames_index;
  470.   char *ptr, *env_infopath, *env_print_command;
  471.  
  472.   nodenames_index = 0;
  473.   nodenames = (char **) xmalloc ((nodenames_size = 1) * sizeof (char *));
  474.   nodenames[0] = (char *)NULL;
  475.  
  476.   env_infopath = getenv ("INFOPATH");
  477.   env_print_command = getenv ("INFO_PRINT_COMMAND");
  478.  
  479.   filename[0] = '\0';
  480.  
  481.   if (env_infopath && *env_infopath)
  482.     infopath = savestring (env_infopath);
  483.   else
  484.     infopath = savestring (DEFAULT_INFOPATH);
  485.  
  486.   if (env_print_command && *env_print_command)
  487.     print_command = savestring (env_print_command);
  488.   else
  489.     print_command = savestring (INFO_PRINT_COMMAND);
  490.  
  491.   while ((c = getopt_long (argc, argv, "d:n:f:o:", long_options, &ind)) != EOF)
  492.     {
  493.       if (c == 0 && long_options[ind].flag == 0)
  494.     c = long_options[ind].val;
  495.       switch (c)
  496.     {
  497.     case 0:
  498.       break;
  499.       
  500.     case 'd':
  501.       free (infopath);
  502.       infopath = savestring (optarg);
  503.       break;
  504.       
  505.     case 'n':
  506.  
  507.       if (nodenames_index + 2 > nodenames_size)
  508.         nodenames = (char **)
  509.           xrealloc (nodenames, (nodenames_size += 10) * sizeof (char *));
  510.  
  511.       nodenames[nodenames_index++] = optarg;
  512.       nodenames[nodenames_index] = (char *)NULL;
  513.       break;
  514.       
  515.     case 'f':
  516.       strncpy (filename, optarg, FILENAME_LEN);
  517.       break;
  518.       
  519.     case 'o':
  520.       strncpy (dumpfile, optarg, FILENAME_LEN);
  521.       break;
  522.       
  523.     default:
  524.       usage ();
  525.     }
  526.     }
  527.  
  528.   /* If the user specified `--version' then simply show the version
  529.      info at this time and exit. */
  530.   if (version_flag)
  531.     {
  532.       show_version_info (stdout);
  533.       exit (0);
  534.     }
  535.  
  536.   /* Okay, flags are parsed.  Get possible Info menuname. */
  537.  
  538.  
  539. #ifdef atarist
  540.   /* Convert names to avoid '\\' and '/' mixups */
  541.   (void) dos2unx(filename, filename);
  542.   /*
  543.    * ':' can be a part of a file name
  544.    */
  545.   if (*filename) 
  546.     {
  547.       size_t dlen;
  548.       char *temp;
  549.  
  550.       if ((ptr = rindex (filename,'/')) == NULL )
  551.       if ((ptr = index (filename,':')) != NULL )
  552.           ptr += 1;
  553.       if (ptr != NULL)
  554.         {
  555.       /* Add filename's directory to path. */
  556.        dlen = ptr - filename;
  557.        temp = (char *) xmalloc (2 + dlen + strlen (infopath));
  558.        (void) strncpy (temp, filename, dlen);
  559.        temp[dlen] = ',';
  560.        (void) strcpy (temp + dlen + 1, infopath);
  561.        free (infopath);
  562.        infopath = temp;
  563.        if ('/' == *ptr)
  564.            dlen += 1;
  565.        (void) strcpy (filename, filename + dlen);
  566.     }
  567.     }
  568. #else /* !atarist */
  569.  
  570.   if (*filename && (ptr = rindex (filename,'/')) != NULL )
  571.     {
  572.       /* Add filename's directory to path. */
  573.       char *temp;
  574.  
  575.       temp = (char *) xmalloc (2 + strlen (filename) + strlen (infopath));
  576.       strncpy (temp, filename, ptr - filename);
  577.       sprintf (temp + (ptr - filename), ":%s", infopath);
  578.       free (infopath);
  579.       infopath = temp;
  580.     }
  581. #endif /* !atarist */
  582.  
  583.   /* Start with DIR or whatever was specified. */
  584.   if (!get_node (filename, (nodenames[0] == NULL) ? "Top" : nodenames[0], 0))
  585.     {
  586.       if (filename[0])
  587.     {
  588.       fprintf (stderr, "Try just plain `info'.\n");
  589.       exit (1);
  590.     }
  591.       else
  592.     {
  593.       strcpy (filename, "DIR");
  594.  
  595.       if (!get_node ((char *)NULL, (char *)NULL, 1))
  596.         {
  597.           fprintf (stderr,
  598.                "%s: Cannot find \"%s\", anywhere along the search ",
  599.                argv[0], filename);
  600.           fprintf (stderr, "path of\n\"%s\".\n", infopath);
  601.           exit (1);
  602.         }
  603.     }
  604.     }
  605.  
  606.   totally_inhibit_errors = 0;
  607.  
  608.   for (ind = 1 ; ind < nodenames_index ; ind++)
  609.     get_node (filename, nodenames[ind], 0);
  610.  
  611.   nodename = nodenames[nodenames_index > 0 ? nodenames_index - 1 : 0];
  612.   if (!nodename)
  613.     {
  614.       nodename = (char *) xmalloc (NODENAME_LEN);
  615.       *nodename = '\0';
  616.     }
  617.   
  618.   if (optind != argc)
  619.     {
  620.       putchar ('\n');
  621.       while (optind != argc)
  622.     {
  623.       if (!build_menu ())
  624.           {
  625.           display_error ("There is no menu in node \"%s\"",
  626.                  current_info_node );
  627.           no_such_node++;
  628.           break;
  629.         }
  630.       else if (!find_menu_node (argv[optind], nodename))
  631.         {
  632.           display_error
  633.         ("There is no menu entry for \"%s\" in node \"%s\"",
  634.          argv[optind], nodename );
  635.           no_such_node++;
  636.           break;
  637.         }
  638.       else if (!get_node ((char *)NULL, nodename, 0))
  639.         {
  640.           no_such_node++;
  641.           break;
  642.         }
  643.       else
  644.          {
  645. #if 1
  646.           /* RMS says not to type this stuff out because he expects
  647.          programs to call Info instead of interactive users. */
  648.           printf ("%s.. ",argv[optind]);
  649.           fflush (stdout);
  650. #endif
  651.           optind++;
  652.         }
  653.     }
  654.     }
  655.  
  656.   /* If we are outputting to a file, and the node was not found, exit. */
  657.   if (no_such_node && dumpfile[0])
  658.     exit (1);
  659.   else
  660.     begin_info_session ();
  661.  
  662.   return (0);
  663. }
  664.  
  665. void
  666. usage ()
  667. {
  668.   fprintf (stderr,"%s\n%s\n%s\n%s\n",
  669. "Usage: info [-d dir-path] [-f info-file] [-n node-name -n node-name ...]",
  670. "            [-o output-file] [--directory dir-path] [--file info-file]",
  671. "            [--node node-name --node node-name ...] [--version]",
  672. "            [--output output-file] [menu-selection...]");
  673.   exit (1);
  674. }
  675.  
  676. #ifndef atarist
  677. #define VERSION_STR    "This is GNU Info version %d.%d.\n"
  678. #else  /* atarist */
  679. #define VERSION_STR    "This is GNU Info version %d.%d (Atari ST).\n"
  680. #endif  /* atarist */
  681. /* Print the version of info to standard output. */
  682. void
  683. show_version_info (stream)
  684.      FILE *stream;
  685. {
  686.   fprintf (stream, VERSION_STR, major_version, minor_version);
  687.   fflush (stream);
  688. }
  689.  
  690. #if defined (SIGTSTP)
  691. Function *old_tstp;
  692. Function *old_ttou, *old_ttin;
  693. #endif /* SIGTSTP */
  694.  
  695. #if defined (SIGWINCH)
  696. Function *old_winch;
  697. #endif /* SIGWINCH */
  698.  
  699. /* Start using Info. */
  700. void
  701. begin_info_session ()
  702. {
  703.   /* If the user just wants to dump the node, then do that. */
  704.   if (dumpfile[0])
  705.     {
  706.       dump_current_node (dumpfile);
  707.       exit (0);
  708.     }
  709.  
  710.   init_terminal_io ();
  711.  
  712.   /* Install handlers for restoring/breaking the screen. */
  713.  
  714.   install_signals ();
  715.   new_echo_area ();
  716.  
  717.   print_string ("Welcome to Info!  Type \"?\" for help. ");
  718.   close_echo_area ();
  719.   toploop ();
  720.   goto_xy (the_window.left, the_window.bottom + 1);
  721.   restore_io ();
  722. }
  723.  
  724. /* What to do before processing a stop signal. */
  725. void
  726. before_stop_signal ()
  727. {
  728.   restore_io ();
  729. }
  730.  
  731. /* What to do after processing a stop signal. */
  732. void
  733. after_stop_signal ()
  734. {
  735.   clear_screen ();
  736.   display_page ();
  737.   goto_xy (the_window.ch, the_window.cv);
  738.   opsys_init_terminal ();
  739. }
  740.  
  741. /* Do the right thing with this signal. */
  742. SigHandler
  743. info_signal_handler (sig)
  744.      int sig;
  745. {
  746.   switch (sig)
  747.     {
  748. #if defined (SIGTSTP)
  749.     case SIGTSTP:
  750.     case SIGTTOU:
  751.     case SIGTTIN:
  752.       before_stop_signal ();
  753.       signal (sig, SIG_DFL);
  754. #if !defined (USG)
  755.       sigsetmask (sigblock (0) & ~sigmask (sig));
  756. #endif /* !USG */
  757.       kill (getpid (), sig);
  758.       after_stop_signal ();
  759.       signal (sig, info_signal_handler);
  760.       break;
  761. #endif /* SIGTSTP */
  762.  
  763. #if defined (SIGWINCH)
  764.     case SIGWINCH:
  765.       /* Window has changed.  Get the size of the new window, and rebuild our
  766.          window chain. */
  767.       {
  768.     extern char *widest_line;
  769.     extern WINDOW terminal_window;
  770.     extern WINDOW_LIST *window_stack;
  771.     extern int terminal_rows, terminal_columns;
  772.     int delta_width, delta_height, right, bottom;
  773.  
  774.     right = get_terminal_columns ();
  775.     bottom = get_terminal_rows ();
  776.  
  777.     delta_width = right - terminal_columns;
  778.     delta_height = bottom - terminal_rows;
  779.  
  780.     terminal_columns = right;
  781.     terminal_rows = bottom;
  782.  
  783.     /* Save current window, whatever it is. */
  784.     push_window ();
  785.  
  786.     /* Change the value of the widest_line. */
  787.     free (widest_line);
  788.     widest_line = (char *) xmalloc (right);
  789.  
  790.     /* Make the new window.  Map over all windows in window list. */
  791.     {
  792.       WINDOW_LIST *wind = window_stack;
  793.       extern WINDOW modeline_window;
  794.  
  795.       while (wind != (WINDOW_LIST *)NULL)
  796.         {
  797.           adjust_wind ((WINDOW *)wind, delta_width, delta_height);
  798.           wind = wind->next_window;
  799.         }
  800.  
  801.       /* Adjust the other windows that we know about. */
  802.       adjust_wind (&terminal_window, delta_width, delta_height);
  803.       adjust_wind (&echo_area, delta_width, delta_height);
  804.       adjust_wind (&modeline_window, delta_width, delta_height);
  805.     }
  806.  
  807.     /* Clear and redisplay the entire terminal window. */
  808.     set_window (&terminal_window);
  809.     clear_screen ();
  810.  
  811.     /* Redisplay the contents of the screen. */
  812.     with_output_to_window (&terminal_window, (Function *)display_page);
  813.  
  814.     /* Get back the current window. */
  815.     pop_window ();
  816.       }
  817.       break;
  818. #endif /* SIGWINCH */
  819.  
  820.     case SIGINT:
  821.       restore_io ();
  822.       exit (1);
  823.       break;
  824.     }
  825. }
  826.  
  827. void
  828. install_signals ()
  829. {
  830. #if defined (SIGTSTP)
  831.   old_tstp = (Function *) signal (SIGTSTP, info_signal_handler);
  832.   old_ttou = (Function *) signal (SIGTTOU, info_signal_handler);
  833.   old_ttin = (Function *) signal (SIGTTIN, info_signal_handler);
  834. #endif /* SIGTSTP */
  835.  
  836. #if defined (SIGWINCH)
  837.   old_winch = (Function *) signal (SIGWINCH, info_signal_handler);
  838. #endif /* SIGWINCH */
  839.  
  840.   signal (SIGINT, info_signal_handler);
  841. }
  842.  
  843. void
  844. adjust_wind (wind, delta_width, delta_height)
  845.      WINDOW *wind;
  846.      int delta_width, delta_height;
  847. {
  848.   wind->right += delta_width;
  849.   wind->bottom += delta_height;
  850.   wind->ch += delta_width;
  851.   wind->cv += delta_height;
  852.  
  853.   /* Ugly hack to fix busted windows code.  If the window we are adjusting
  854.      already has a TOP offset, then adjust that also. */
  855.   if (wind->top)
  856.     wind->top += delta_height;
  857. }
  858.  
  859. /* **************************************************************** */
  860. /*                                    */
  861. /*            Completing Things                */
  862. /*                                    */
  863. /* **************************************************************** */
  864.  
  865. /*** already defined above
  866. typedef struct completion_entry
  867. {
  868.   char *identifier;
  869.   char *data;
  870.   struct completion_entry *next;
  871. }                COMP_ENTRY;
  872. ***/ 
  873.  
  874. /* The linked list of COMP_ENTRY structures that you create. */
  875. COMP_ENTRY *completion_list = (COMP_ENTRY *) NULL;
  876.  
  877. /* The vector of COMP_ENTRY pointers that COMPLETE returns. */
  878. COMP_ENTRY **completions = NULL;
  879.  
  880. /* The number of elements in the above vector. */
  881. int completion_count;
  882.  
  883. /* Initial size of COMPLETIONS array. */
  884. #define INITIAL_COMPLETIONS_CORE_SIZE 200
  885.  
  886. /* Current size of the completion array in core. */
  887. int completions_core_size = 0;
  888.  
  889. /* Ease the typing task.  Another name for the I'th
  890.    IDENTIFIER of COMPLETIONS. */
  891. #define completion_id(i) ((completions[(i)])->identifier)
  892.  
  893. /* The number of completions that can be present before the help
  894.    function starts asking you about whether it should print them
  895.    all or not. */
  896. int completion_query_threshold = 100;
  897.  
  898. void
  899. free_completion_list ()
  900. {
  901.   COMP_ENTRY *temp;
  902.   while (completion_list)
  903.     {
  904.       temp = completion_list;
  905.  
  906.       if (completion_list->identifier)
  907.     free (completion_list->identifier);
  908.  
  909.       if (completion_list->data)
  910.     free (completion_list->data);
  911.  
  912.       completion_list = completion_list->next;
  913.       free (temp);
  914.     }
  915. }
  916.  
  917. /* Add a single completion to COMPLETION_LIST.
  918.    IDENTIFIER is the string that the user should type.
  919.    DATA should just be a pointer to some random data that you wish to
  920.    have associated with the identifier, but I'm too stupid for that, so
  921.    it must be a string as well.  This allocates the space for the strings
  922.    so you don't necessarily have to. */
  923. void
  924. add_completion (identifier, data)
  925.      char *identifier, *data;
  926. {
  927.   COMP_ENTRY *temp = (COMP_ENTRY *) xmalloc (sizeof (COMP_ENTRY));
  928.  
  929.   temp->identifier = (char *) xmalloc (strlen (identifier) + 1);
  930.   strcpy (temp->identifier, identifier);
  931.  
  932.   temp->data = (char *) xmalloc (strlen (data) + 1);
  933.   strcpy (temp->data, data);
  934.  
  935.   temp->next = completion_list;
  936.   completion_list = temp;
  937. }
  938.  
  939. /* Function for reading a line.  Supports completion on COMPLETION_LIST
  940.    if you pass COMPLETING as non-zero.  Prompt is either a prompt or
  941.    NULL, LINE is the place to store the characters that are read.
  942.    LINE may start out already containing some characters; if so, they
  943.    are printed.  MAXCHARS tells how many characters can fit in the
  944.    buffer at LINE.  readline () returns zero if the user types the
  945.    abort character.  LINE is returned with a '\0' at the end, not a '\n'. */
  946. int
  947. readline (prompt, line, maxchars, completing)
  948.      const char *prompt;
  949.      char *line;
  950.      int maxchars;
  951.      int completing;
  952. {
  953.   int character;
  954.   int readline_ch, readline_cv;
  955.   size_t current_len = strlen (line);
  956.   int just_completed = 0;        /* Have we just done a completion? */
  957.   int meta_flag = 0;
  958.  
  959.   new_echo_area ();
  960.  
  961.   if (prompt)
  962.     print_string ("%s", prompt);
  963.  
  964.   readline_ch = the_window.ch;
  965.   readline_cv = the_window.cv;
  966.  
  967.   print_string ("%s", line);
  968.  
  969.   while (1)
  970.     {
  971.       line[current_len] = '\0';
  972.       goto_xy (readline_ch, readline_cv);
  973.       print_string ("%s", line);
  974.       clear_eol ();
  975.  
  976.       if (just_completed)
  977.     just_completed--;
  978.  
  979.       character = blink_cursor ();
  980.       if (meta_flag)
  981.     {
  982.       character = META (character);
  983.       meta_flag = 0;
  984.     }
  985.  
  986.       if (META_P (character))
  987.     character = META (to_upper (UNMETA (character)));
  988.  
  989.       switch (character)
  990.     {
  991.     case EOF:
  992.       character = '\n';
  993.  
  994.     case ESC:
  995.       meta_flag++;
  996.       break;
  997.  
  998.     case META (DELETE):
  999.     case CTRL ('W'):
  1000.       while (current_len && line[current_len] == SPACE)
  1001.         current_len--;
  1002.  
  1003.       if (!current_len)
  1004.         break;
  1005.  
  1006.       while (current_len && line[current_len] != SPACE)
  1007.         current_len--;
  1008.  
  1009.       break;
  1010.  
  1011.     case CTRL ('U'):
  1012.       current_len = 0;
  1013.       break;
  1014.  
  1015.     case '\b':
  1016.     case 0x07f:
  1017.       if (current_len)
  1018.         current_len--;
  1019.       else
  1020.         ding ();
  1021.       break;
  1022.  
  1023.     case '\n':
  1024.     case '\r':
  1025.       if (completing)
  1026.         {
  1027.           extern int completion_count;
  1028.  
  1029.           try_complete (line);
  1030.  
  1031.           if (completion_count >= 1)
  1032.         {
  1033.           close_echo_area ();
  1034.           return (1);
  1035.         }
  1036.           else
  1037.         {
  1038.           current_len = strlen (line);
  1039.           break;
  1040.         }
  1041.         }
  1042.       else
  1043.         {
  1044.           close_echo_area ();
  1045.           return (1);
  1046.         }
  1047.  
  1048.     case ABORT_CHAR:
  1049.       ding ();
  1050.  
  1051.       if (current_len)
  1052.         {
  1053.           current_len = 0;
  1054.         }
  1055.       else
  1056.         {
  1057.           close_echo_area ();
  1058.           clear_echo_area ();
  1059.           return (0);
  1060.         }
  1061.       break;
  1062.  
  1063.     case ' ':
  1064.     case '\t':
  1065.     case '?':
  1066.       if (completing)
  1067.         {
  1068.           extern int completion_count;
  1069.  
  1070.           if (character == '?' || just_completed)
  1071.         {
  1072.           help_possible_completions (line);
  1073.           break;
  1074.         }
  1075.           else
  1076.         {
  1077.           char temp_line[NODENAME_LEN];
  1078.           strcpy (temp_line, line);
  1079.           try_complete (line); just_completed = 2;
  1080.           if (completion_count != 1 && character == SPACE)
  1081.             {
  1082.               if (strcmp (temp_line, line) == 0)
  1083.             {
  1084.               line[current_len] = SPACE;
  1085.               line[current_len + 1] = '\0';
  1086.               strcpy (temp_line, line);
  1087.               try_complete (line);
  1088.               if (completion_count == 0)
  1089.                 {
  1090.                   line[current_len] = '\0';
  1091.                   ding ();
  1092.                 }
  1093.             }
  1094.             }
  1095.           current_len = strlen (line);
  1096.           if (completion_count == 0)
  1097.             ding ();
  1098.           break;
  1099.         }
  1100.         }
  1101.       /* Do *NOT* put anything in-between the completing cases and
  1102.          the default: case.  No.  Because the SPC, TAB and `?' get
  1103.          treated as normal characters by falling through the
  1104.          "if (completing)" test above. */
  1105.     default:
  1106.       if (!CTRL_P (character) &&
  1107.           !META_P (character) &&
  1108.           current_len < maxchars)
  1109.         line[current_len++] = character;
  1110.       else
  1111.         ding ();
  1112.     }
  1113.     }
  1114. }
  1115.  
  1116. /* Initialize whatever the completer is using. */
  1117. void
  1118. init_completer ()
  1119. {
  1120.   if (completions_core_size != INITIAL_COMPLETIONS_CORE_SIZE)
  1121.     {
  1122.       if (completions)
  1123.     free (completions);
  1124.  
  1125.       completions = (COMP_ENTRY **)
  1126.     xmalloc ((sizeof (COMP_ENTRY *))
  1127.          * (completions_core_size = INITIAL_COMPLETIONS_CORE_SIZE));
  1128.     }
  1129.   completion_count = 0;
  1130. }
  1131.  
  1132. /* Reverse the completion list passed in LIST, and
  1133.    return a pointer to the new head. */
  1134. COMP_ENTRY *
  1135. reverse_list (list)
  1136.      COMP_ENTRY *list;
  1137. {
  1138.   COMP_ENTRY *next;
  1139.   COMP_ENTRY *prev = (COMP_ENTRY *) NULL;
  1140.  
  1141.   while (list)
  1142.     {
  1143.       next = list->next;
  1144.       list->next = prev;
  1145.       prev = list;
  1146.       list = next;
  1147.     }
  1148.   return (prev);
  1149. }
  1150.  
  1151. /* Remember the possible completion passed in POINTER on the
  1152.    completions list. */
  1153. void
  1154. remember_completion (pointer)
  1155.      COMP_ENTRY *pointer;
  1156. {
  1157.   if (completion_count == completions_core_size)
  1158.     {
  1159.       COMP_ENTRY **temp = (COMP_ENTRY **)
  1160.     xrealloc (completions, ((sizeof (COMP_ENTRY *))
  1161.                    * (completions_core_size +=
  1162.                   INITIAL_COMPLETIONS_CORE_SIZE)));
  1163.       if (!temp)
  1164.     {
  1165.       display_error ("Too many completions (~d)!  Out of core!",
  1166.              completion_count);
  1167.       return;
  1168.     }
  1169.       else
  1170.     completions = temp;
  1171.     }
  1172.   completions[completion_count++] = pointer;
  1173. }
  1174.  
  1175. /* Complete TEXT from identifiers in LIST.  Place the resultant
  1176.    completions in COMPLETIONS, and the number of completions in
  1177.    COMPLETION_COUNT. Modify TEXT to contain the least common
  1178.    denominator of all the completions found. */
  1179. int
  1180. complete (text, list)
  1181.      char *text;
  1182.      COMP_ENTRY *list;
  1183. {
  1184.   size_t i, idx;
  1185.   size_t low_match;
  1186.   size_t string_length = strlen (text);
  1187.  
  1188.   init_completer ();
  1189.   low_match = 100000;        /* Some large number. */
  1190.  
  1191.   while (list)
  1192.     {
  1193.       if (strnicmp (text, list->identifier, string_length) == 0)
  1194.     remember_completion (list);
  1195.       list = list->next;
  1196.     }
  1197.  
  1198.   if (completion_count == 0)
  1199.     return (0);
  1200.  
  1201.   if (completion_count == 1)
  1202.     {                /* One completion */
  1203.       strcpy (text, completion_id (0));
  1204.       return (1);
  1205.     }
  1206.  
  1207.   /* Else find the least common denominator */
  1208.  
  1209.   idx = 1;
  1210.  
  1211.   while (idx < completion_count)
  1212.     {
  1213.       int c1, c2;
  1214.       for (i = 0;
  1215.        (c1 = to_lower (completion_id (idx - 1)[i])) &&
  1216.        (c2 = to_lower (completion_id (idx)[i]));
  1217.        i++)
  1218.     if (c1 != c2)
  1219.       break;
  1220.  
  1221.       if (low_match > i)
  1222.     low_match = i;
  1223.       idx++;
  1224.     }
  1225.  
  1226.   strncpy (text, completion_id (0), low_match);
  1227.   text[low_match] = '\0';
  1228.   return (1);
  1229. }
  1230.  
  1231. /* Complete TEXT from the completion structures in COMPLETION_LIST. */
  1232. int
  1233. try_complete (text)
  1234.      char *text;
  1235. {
  1236.   return (complete (text, completion_list));
  1237. }
  1238.  
  1239. /* The function that prints out the possible completions. */
  1240. void
  1241. help_possible_completions (text)
  1242.      char *text;
  1243. {
  1244.   char temp_string[2000];
  1245.  
  1246.   goto_xy (the_window.left, the_window.top);
  1247.   strcpy (temp_string, text);
  1248.   try_complete (temp_string);
  1249.  
  1250.   open_typeout ();
  1251.  
  1252.   if (completion_count == 0)
  1253.     {
  1254.       print_string ("There are no possible completions.\n");
  1255.       goto print_done;
  1256.     }
  1257.  
  1258.   if (completion_count == 1)
  1259.     {
  1260.       print_string
  1261.     ("The only possible completion of what you have typed is:\n\n");
  1262.       print_string ("%s", completion_id(0));
  1263.       goto print_done;
  1264.     }
  1265.  
  1266.   if (completion_count >= completion_query_threshold)
  1267.     {
  1268.       print_string
  1269.     ("\nThere are %d completions.  Do you really want to see them all",
  1270.      completion_count);
  1271.  
  1272.       if (!get_y_or_n_p ())
  1273.     return;
  1274.     }
  1275.  
  1276.   print_string ("\nThe %d completions of what you have typed are:\n\n",
  1277.         completion_count);
  1278.  
  1279.   {
  1280.     int idx = 0;
  1281.     int counter = 0;
  1282.     int columns = (the_window.right - the_window.left) / 30;
  1283.  
  1284.     while (idx < completion_count)
  1285.       {
  1286.     if (counter == columns)
  1287.       {
  1288.         charout ('\n');
  1289.         counter = 0;
  1290.       }
  1291.     indent_to (counter * 30);
  1292.     print_string ("%s", completion_id (idx));
  1293.     counter++;
  1294.     idx++;
  1295.       }
  1296.   }
  1297.  
  1298. print_done:
  1299.   print_string ("\n\n-----------------\n");
  1300.   close_typeout ();
  1301. }
  1302.  
  1303. /* Return the next file that should be searched, or NULL if we are at the end
  1304.    of the info file. If the FILE argument is provided, begin the search there,
  1305.    if REWIND is non-zero start the search at the beginning of the list.
  1306.  
  1307.    The list is the one built by an indirect tag table, on the supposition
  1308.    that those files form a logical set to search if we are in one of them.
  1309.    If no such list is current (either it doesn't exist, or FILE isn't on
  1310.    it) the search list is set to be last_loaded_info_file */
  1311. char *
  1312. next_info_file (file, rewind)
  1313.      char *file;        /* file to set `next' to. May be NULL. */
  1314.      int rewind;        /* should I rewind the file list?  */
  1315. {
  1316.   static int index = -1;
  1317.  
  1318.   if (file != NULL)
  1319.     {
  1320.       char *ptr = rindex (file,'/');
  1321.  
  1322.       if (ptr != NULL)
  1323.     file = ptr + 1;
  1324.  
  1325.       for (index = 0;
  1326.        index < MAX_INDIRECT_FILES &&
  1327.        indirect_list[index].filename != (char *)NULL;
  1328.        index++)
  1329.     {
  1330.       if (strcmp (file, indirect_list[index].filename) == 0)
  1331.         return (file);
  1332.     }
  1333.  
  1334.       /* OK, we are not on the current indirect_list. This means that
  1335.      we have switched to another node that has no indirect list,
  1336.      so forget the old one. */
  1337.       for (index = 0;
  1338.        index < MAX_INDIRECT_FILES &&
  1339.        indirect_list[index].filename != (char *)NULL;
  1340.        index++)
  1341.     {
  1342.       free (indirect_list[index].filename);
  1343.       indirect_list[index].filename = (char *)NULL;
  1344.     }
  1345.       return (indirect_list[0].filename = savestring (file));
  1346.     }
  1347.   else if (rewind)
  1348.     {
  1349.       index = 0;
  1350.  
  1351.       if (indirect_list[0].filename == (char *)NULL)
  1352.     indirect_list[0].filename = savestring (last_loaded_info_file);
  1353.     }
  1354.   else
  1355.     index++;
  1356.   
  1357.   if (index < MAX_INDIRECT_FILES &&
  1358.       indirect_list[index].filename != (char *)NULL)
  1359.     return (indirect_list[index].filename);
  1360.  
  1361.   index = -1;
  1362.   return (NULL);
  1363. }
  1364.  
  1365. /* **************************************************************** */
  1366. /*                                    */
  1367. /*            Getting Nodes                    */
  1368. /*                                    */
  1369. /* **************************************************************** */
  1370.  
  1371. /* A node name looks like:
  1372.    Node: nodename with spaces but not a comma,
  1373. or Node: (filename-containing-node)node-within-file
  1374. or Node: (filename)
  1375.  
  1376.    The latter case implies a nodename of "Top".  All files are
  1377.    supposed to have one.
  1378.  
  1379.    Lastly, the nodename specified could be "*", which specifies the
  1380.    entire file. */
  1381.  
  1382. /* Return the directory portion of FILENAME, i.e., everything before the
  1383.    last slash. */
  1384. static char *
  1385. file_directory (filename)
  1386.      char *filename;
  1387. {
  1388.   register char *scan;
  1389.   register size_t length;
  1390.   char *result;
  1391.  
  1392.   scan = filename;
  1393.  
  1394.   while (*scan++ != '\0');
  1395.  
  1396.   while (1)
  1397.     {
  1398.       if (scan == filename)
  1399.     break;
  1400.  
  1401.       if ((*--scan) == '/')
  1402.     {
  1403.       scan++;
  1404.       break;
  1405.     }
  1406.     }
  1407.  
  1408.   length = scan - filename;
  1409.   result = (char *) xmalloc (length + 1);
  1410.   strncpy (result, filename, length);
  1411.   result[length] = '\0';
  1412.  
  1413.   return (result);
  1414. }
  1415.  
  1416. /* Given FILENAME and DIRECTORY return a newly allocated string which
  1417.    is either the two concatenated, or simply FILENAME if it is absolute
  1418.    already. */
  1419. static char *
  1420. file_absolutize (filename, directory)
  1421.      char *filename, *directory;
  1422. {
  1423.   register size_t filename_len, directory_len;
  1424.   char *result;
  1425.  
  1426.   if (filename[0] == '/')
  1427.     return (savestring (filename));
  1428.  
  1429.   filename_len = strlen (filename);
  1430.   directory_len = strlen (directory);
  1431.   result = (char *) xmalloc (directory_len + filename_len + 1);
  1432.  
  1433.   strcpy (result, directory);
  1434.   strcat (result, filename);
  1435.  
  1436.   return (result);
  1437. }
  1438.  
  1439. /* Load FILENAME.  If REMEMBER_NAME is non-zero, then remember the
  1440.    loaded filename in CURRENT_INFO_FILE.  In either case, remember
  1441.    the name of this file in LAST_LOADED_INFO_FILE. */
  1442. int
  1443. get_info_file (filename, remember_name)
  1444.      char *filename;
  1445.      int remember_name;
  1446. {
  1447.   FILE *input_stream;
  1448.   struct stat file_info;
  1449.   size_t pointer;
  1450.   int result;
  1451.   char tempname[FILENAME_LEN];
  1452.  
  1453.   /* Get real filename. */
  1454.   strcpy (tempname, opsys_filename (filename));
  1455.  
  1456.   /* If the file doesn't exist, try again with the name in lower case. */
  1457.   result = stat (tempname, &file_info);
  1458.  
  1459.   if (result < 0)
  1460.     {
  1461.       register int i;
  1462.       char *lowered_name;
  1463.  
  1464.       lowered_name = (char *)xmalloc (1 + strlen (filename));
  1465.  
  1466.       for (i = 0; lowered_name[i] = to_lower (filename[i]); i++)
  1467.     ;
  1468.  
  1469.       strcpy (tempname, opsys_filename (lowered_name));
  1470.       result = stat (tempname, &file_info);
  1471.     }
  1472.  
  1473.   /* See if this file is the last loaded one. */
  1474.   if (!result && (strcmp (last_loaded_info_file, tempname) == 0))
  1475.     return (1);
  1476.  
  1477.   /* Now try to open the file. */
  1478.   if (result || (input_stream = fopen (tempname, RMODE)) == NULL)
  1479.     {
  1480.       file_error (tempname);
  1481.       return (0);
  1482.     }
  1483.  
  1484.   /* If we already have a file loaded, then free it first. */
  1485.   if (info_file)
  1486.     {
  1487.       free (info_file);
  1488.  
  1489.       if (!indirect)
  1490.     {
  1491.       /* Then the tag table is also no longer valid. */
  1492.       tag_table = (char *) NULL;
  1493.     }
  1494.     }
  1495.  
  1496.   /* Read the contents of the file into a new buffer. */
  1497.  
  1498.   info_file = (char *) xmalloc (info_buffer_len = file_info.st_size);
  1499.   info_buffer_len = fread (info_file, 1, info_buffer_len, input_stream);
  1500.   fclose (input_stream);
  1501. #ifdef atarist
  1502.   /* clean possible MessyDOS droppings and adjust info_buffer_len */
  1503.   {
  1504.     char *text_pos = info_file;
  1505.     char *pos = text_pos;
  1506.     char *text_end = pos + info_buffer_len;
  1507.     while (text_pos < text_end) {
  1508.       if ('\r' != (*pos = *text_pos++))
  1509.      pos += 1;
  1510.     }
  1511.     info_buffer_len = pos - info_file;
  1512.     /* return not needed memory */
  1513.     info_file = xrealloc(info_file, info_buffer_len);
  1514.   }
  1515. #endif
  1516.   strcpy (last_loaded_info_file, tempname);
  1517.   if (remember_name)
  1518.     {
  1519.       strcpy (current_info_file, tempname);
  1520.       if (indirect)
  1521.     {
  1522.       indirect = 0;
  1523.       free (tag_table);
  1524.     }
  1525.     }
  1526.   else
  1527.     return (1);
  1528.  
  1529.   /* Force redisplay, since we are looking at a new file. */
  1530.   window_bashed = 1;
  1531.  
  1532.   /* The file has been read, and we don't know anything about it.
  1533.      Find out if it contains a tag table. */
  1534.  
  1535.   tag_table = NULL;        /* assume none. */
  1536.   indirect = 0;
  1537.   tag_buffer_len = 0;
  1538.  
  1539.   set_search_constraints (info_file, info_buffer_len);
  1540.  
  1541.   /* Go to the last few lines in the file. */
  1542.   pointer = back_lines (8, info_buffer_len);
  1543.   pointer = search_forward (TAG_TABLE_END_STRING, pointer);
  1544.  
  1545.   if ((long)pointer > -1)
  1546.     {
  1547.       /* Then there is a tag table.  Find the start of it,
  1548.      and remember that. */
  1549.       pointer = search_backward (TAG_TABLE_BEG_STRING, pointer);
  1550.  
  1551.       /* Handle error for malformed info file. */
  1552.       if ((long)pointer < 0)
  1553.     display_error ("Start of tag table not found!");
  1554.       else
  1555.     {
  1556.       /* No problem.  If this file is an indirect file, then the contents
  1557.          of the tag table must remain in RAM the entire time.  Otherwise,
  1558.          we can flush the tag table with the file when the file is flushed.
  1559.          So, if indirect, remember that, and copy the table to another
  1560.          place.*/
  1561.  
  1562.       size_t indirect_check = forward_lines (2, pointer);
  1563.  
  1564.       tag_table = info_file + pointer;
  1565.       tag_buffer_len = info_buffer_len - pointer;
  1566.  
  1567.       /* Shorten the search constraints. */
  1568.       info_buffer_len = pointer;
  1569.  
  1570.       if (looking_at ("(Indirect)\n", indirect_check))
  1571.         {
  1572.           /* We have to find the start of the indirect file's
  1573.          information. */
  1574.           tag_table = (char *) xmalloc (tag_buffer_len);
  1575.  
  1576.           bcopy (&info_file[indirect_check], tag_table, tag_buffer_len);
  1577.  
  1578.           /* Find the list of filenames. */
  1579.           indirect_top = search_backward ("Indirect:\n", indirect_check);
  1580.           if ((long)indirect_top < 0)
  1581.         {
  1582.           free (tag_table);
  1583.           tag_table = (char *) NULL;
  1584.           display_error ("Start of INDIRECT tag table not found!");
  1585.           return (0);
  1586.         }
  1587.  
  1588.           /* Remember the filenames, and their byte offsets. */
  1589.           {
  1590.         /* Index into the filename/offsets array. */
  1591.         int idx;
  1592.         size_t temp_first_byte;
  1593.         char temp_filename[FILENAME_LEN];
  1594.         char *directory = file_directory (tempname);
  1595.  
  1596.         info_buffer_len = indirect_top;
  1597.  
  1598.         /* For each line, scan the info into globals.  Then save
  1599.                the information in the INDIRECT_INFO structure. */
  1600.  
  1601.         for (idx = 0; idx < MAX_INDIRECT_FILES &&
  1602.              indirect_list[idx].filename != (char *) NULL;
  1603.              idx++)
  1604.             {
  1605.              free (indirect_list[idx].filename);
  1606.              indirect_list[idx].filename = (char *) NULL;
  1607.           }
  1608.         
  1609.         for (idx = 0;info_file[indirect_top] != info_separator_char &&
  1610.              idx < MAX_INDIRECT_FILES;)
  1611.           {
  1612.             indirect_top = forward_lines (1, indirect_top);
  1613.             if (info_file[indirect_top] == info_separator_char)
  1614.               break;
  1615.  
  1616.             /* Ignore blank lines. */
  1617.             if (info_file[indirect_top] == '\n')
  1618.               continue;
  1619.  
  1620.             sscanf (&info_file[indirect_top], "%s%lu",
  1621.                 temp_filename, &temp_first_byte);
  1622.  
  1623.             if (strlen (temp_filename))
  1624.               {
  1625.             temp_filename[strlen (temp_filename) - 1] = '\0';
  1626.             indirect_list[idx].filename =
  1627.               file_absolutize (temp_filename, directory);
  1628.             indirect_list[idx].first_byte = temp_first_byte;
  1629.             idx++;
  1630.               }
  1631.           }
  1632.  
  1633.         free (directory);
  1634.  
  1635.         /* Terminate the table. */
  1636.         if (idx == MAX_INDIRECT_FILES)
  1637.           {
  1638.             display_error
  1639.               ("Sorry, the INDIRECT file array isn't large enough.");
  1640.             idx--;
  1641.           }
  1642.         indirect_list[idx].filename = (char *) NULL;
  1643.           }
  1644.           indirect = 1;
  1645.        } else {
  1646.           ;
  1647.        }
  1648.     }
  1649.     }
  1650.   return (1);
  1651. }
  1652.  
  1653. /* Make current_info_node be NODENAME.  This could involve loading
  1654.    a file, etc.  POPPING is non-zero if we got here because we are
  1655.    popping one level. */
  1656. int
  1657. get_node (filename, nodename, popping)
  1658.      char *nodename;
  1659.      char *filename;
  1660.      int popping;
  1661. {
  1662.   size_t pointer;
  1663.   char internal_filename[FILENAME_LEN];
  1664.   char internal_nodename[NODENAME_LEN];
  1665.  
  1666.   if (nodename && *nodename)
  1667.     {
  1668.       /* Maybe nodename looks like: (filename)nodename, or worse: (filename).
  1669.          If so, extract the stuff out. */
  1670.       if (*nodename == '(')
  1671.     {
  1672.       register int temp = 1;
  1673.       char character;
  1674.  
  1675.       filename = internal_filename;
  1676.  
  1677.       while ((character = nodename[temp]) && character != ')')
  1678.         {
  1679.           filename[temp - 1] = character;
  1680.           temp++;
  1681.         }
  1682.  
  1683.       if (character)
  1684.         {
  1685.           filename[temp - 1] = '\0';
  1686.           temp++;        /* skip the closing ')' */
  1687.         }
  1688.  
  1689.       /* We have the filename now.  The nodename follows. */
  1690.       internal_nodename[0] = '\0';
  1691.  
  1692.       while (nodename[temp] == ' ' ||
  1693.          nodename[temp] == '\t' ||
  1694.          nodename[temp] == '\n')
  1695.         temp++;
  1696.  
  1697.       if (nodename[temp])
  1698.         {
  1699.           register int tem1 = 0;
  1700.  
  1701.           while (internal_nodename[tem1++] = nodename[temp++])
  1702.         ;
  1703.         }
  1704.       else if (*filename != '\0')
  1705.         strcpy (internal_nodename,"Top");
  1706.  
  1707.       nodename = internal_nodename;
  1708.     }
  1709.     }
  1710.  
  1711.   if (!popping)
  1712.     push_node (current_info_file, current_info_node, pagetop, nodetop);
  1713.  
  1714.   if (!nodename || !*nodename)
  1715.     {
  1716.       nodename = internal_nodename;
  1717.       strcpy (nodename, "Top");
  1718.     }
  1719.  
  1720.   if (!filename || !*filename)
  1721.     {
  1722.       filename = internal_filename;
  1723.       strcpy (filename, current_info_file);
  1724.     }
  1725.  
  1726.   if (!*filename)
  1727.     strcpy (filename, "DIR");
  1728.  
  1729.   if (!get_info_file (filename, 1))
  1730.     goto node_not_found;
  1731.  
  1732.   if (strcmp (nodename, "*") == 0)
  1733.     {
  1734.       /* The "node" that we want is the entire file. */
  1735.       pointer = 0;
  1736.       goto found_node;
  1737.     }
  1738.   
  1739.   /* If we are using a tag table, see if we can find the nodename in it. */
  1740.   if (tag_table)
  1741.     {
  1742.       pointer = find_node_in_tag_table (nodename, 0);
  1743.       if ((long) pointer < 1)
  1744.     {
  1745.  
  1746.       /* The search through the tag table failed.  Maybe we
  1747.          should try searching the buffer?  Nahh, just barf. */
  1748.     node_not_found:
  1749.       if (popping)
  1750.         return (0);    /* Second time through. */
  1751.  
  1752.       {
  1753.          int save_inhibit = totally_inhibit_errors;
  1754.  
  1755.          totally_inhibit_errors = 0;
  1756.          display_error
  1757.            ("Sorry, unable to find the node \"%s\" in the file \"%s\".",
  1758.         nodename, filename);
  1759.          totally_inhibit_errors = save_inhibit;
  1760.       }
  1761.  
  1762.       current_info_file[0] = '\0';
  1763.       current_info_node[0] = '\0';
  1764.       last_loaded_info_file[0] = '\0';
  1765.       pop_node (internal_filename, internal_nodename, &nodetop, &pagetop);
  1766.       get_node (internal_filename, internal_nodename, 1);
  1767.       return (0);
  1768.     }
  1769.  
  1770.       /* Otherwise, the desired location is right here.
  1771.          Scarf the position byte. */
  1772.       while (tag_table[pointer] != '\177')
  1773.     pointer++;
  1774.  
  1775.       sscanf (&tag_table[pointer + 1], "%lu", &pointer);
  1776.  
  1777.       /* Okay, we have a position pointer.  If this is an indirect file,
  1778.          then we should look through the indirect_list for the first
  1779.          element.first_byte which is larger than this.  Then we can load
  1780.          the specified file, and win. */
  1781.       if (indirect)
  1782.     {
  1783.       /* Find the filename for this node. */
  1784.       int idx;
  1785.       for (idx = 0; idx < MAX_INDIRECT_FILES &&
  1786.            indirect_list[idx].filename != (char *) NULL; idx++)
  1787.         {
  1788.           if (indirect_list[idx].first_byte > pointer)
  1789.         {
  1790.           /* We found it. */
  1791.           break;
  1792.         }
  1793.         }
  1794.       if (!get_info_file (indirect_list[idx - 1].filename, 1))
  1795.         goto node_not_found;
  1796.       pointer -= indirect_list[idx - 1].first_byte;
  1797.  
  1798.       /* Here is code to compensate for the header of an indirect file. */
  1799.       {
  1800.         size_t tt = find_node_start (0);
  1801.         if ((long)tt > -1)
  1802.           pointer += tt;
  1803.       }
  1804.     }
  1805.       else
  1806.     {
  1807.       /* This tag table is *not* indirect.  The filename of the file
  1808.          containing this node is the same as the current file.  The
  1809.          line probably looks like:
  1810.          File: info,  Node: Checking25796 */
  1811.     }
  1812.     }
  1813.   else
  1814.     {
  1815. #if defined (NOTDEF)
  1816.       /* We don't have a tag table.  The node can only be found by
  1817.          searching this file in its entirety.  */
  1818.       if (!get_info_file (filename, 1))
  1819.     return (0);
  1820. #endif /* NOTDEF */
  1821.       pointer = 0;
  1822.     }
  1823.  
  1824.   /* Search this file, using pointer as a good guess where to start. */
  1825.   /* This is the same number that RMS used.  It might be right or wrong. */
  1826.   pointer -= 1000;
  1827.   if ((long)pointer < 0)
  1828.     pointer = 0;
  1829.  
  1830.   pointer = find_node_in_file (nodename, pointer);
  1831.   if ((long)pointer < 0)
  1832.     goto node_not_found;
  1833.  
  1834.   /* We found the node in its file.  Remember exciting information. */
  1835.  
  1836. found_node:
  1837.   back_lines (0, pointer);
  1838.   nodetop = pagetop = pointer;
  1839.   strcpy (current_info_node, nodename);
  1840.   strcpy (current_info_file, filename);
  1841.   get_node_extent ();
  1842.   return (1);
  1843. }
  1844.  
  1845. /* Get the bounds for this node.  NODETOP points to the start of the
  1846.    node. Scan forward looking for info_separator_char, and remember
  1847.    that in NODEBOT. */
  1848. void
  1849. get_node_extent ()
  1850. {
  1851.   size_t idx = nodetop;
  1852.   int character;
  1853.   int do_it_till_end = (strcmp (current_info_node, "*") == 0);
  1854.  
  1855.   nodelines = 0;
  1856.  
  1857. again:
  1858.   while ((idx < info_buffer_len) &&
  1859.      ((character = info_file[idx]) != info_separator_char))
  1860.     {
  1861.       if (character == '\n')
  1862.     nodelines++;
  1863.       idx++;
  1864.     }
  1865.   if (do_it_till_end && idx != info_buffer_len)
  1866.     {
  1867.       idx++;
  1868.       goto again;
  1869.     }
  1870.   nodebot = idx;
  1871. }
  1872.  
  1873. /* Locate the start of a node in the current search_buffer.  Return
  1874.    the offset to the node start, or minus one.  START is the place in
  1875.    the file at where to begin the search. */
  1876. size_t
  1877. find_node_start (start)
  1878.      size_t start;
  1879. {
  1880.   return (search_forward (start_of_node_string, start));
  1881. }
  1882.  
  1883. /* Find NODENAME in TAG_TABLE. */
  1884. size_t
  1885. find_node_in_tag_table (nodename, offset)
  1886.      char *nodename;
  1887.      size_t offset;
  1888. {
  1889.   size_t temp;
  1890.  
  1891.   set_search_constraints (tag_table, tag_buffer_len);
  1892.  
  1893.   temp = offset;
  1894.   while (1)
  1895.     {
  1896.       offset = search_forward (NODE_ID, temp);
  1897.  
  1898.       if ((long)offset < 0)
  1899.     return (offset);
  1900.  
  1901.       temp = skip_whitespace (offset + strlen (NODE_ID));
  1902.  
  1903.       if (strnicmp (tag_table + temp, nodename, strlen (nodename)) == 0)
  1904.     {
  1905.       register char character;
  1906.  
  1907.       character = *(tag_table + temp + strlen (nodename));
  1908.  
  1909.       if (character == '\177' || character == ',')
  1910.         return (temp);
  1911.     }
  1912.     }
  1913. }
  1914.  
  1915. /* Find NODENAME in INFO_FILE. */
  1916. size_t
  1917. find_node_in_file (nodename, offset)
  1918.      char *nodename;
  1919.      size_t offset;
  1920. {
  1921.   size_t temp, last_offset = -1;
  1922.  
  1923.   set_search_constraints (info_file, info_buffer_len);
  1924.  
  1925.   while (1)
  1926.     {
  1927.       offset = find_node_start (offset);
  1928.  
  1929.       if (offset == last_offset)
  1930.     offset = -1;
  1931.       else
  1932.     last_offset = offset;
  1933.  
  1934.       if ((long)offset < 0)
  1935.     return (offset);
  1936.       else
  1937.     temp = forward_lines (1, offset);
  1938.  
  1939.       if (temp == offset)
  1940.     return (-1);        /* At last line now, just a node start. */
  1941.       else
  1942.     offset = temp;
  1943.  
  1944.       temp = string_in_line (NODE_ID, offset);
  1945.  
  1946.       if ((long)temp > -1)
  1947.     {
  1948.       temp = skip_whitespace (temp + strlen (NODE_ID));
  1949.       if (strnicmp (info_file + temp, nodename, strlen (nodename)) == 0)
  1950.         {
  1951.           int check_exact = *(info_file + temp + strlen (nodename));
  1952.  
  1953.           if (check_exact == '\t' ||
  1954.           check_exact == ',' ||
  1955.           check_exact == '.' ||
  1956.           check_exact == '\n')
  1957.         return (offset);
  1958.         }
  1959.     }
  1960.     }
  1961. }
  1962.  
  1963.  
  1964. /* **************************************************************** */
  1965. /*                                    */
  1966. /*            Dumping and Printing Nodes                */
  1967. /*                                    */
  1968. /* **************************************************************** */
  1969.  
  1970. /* Make a temporary filename based on STARTER and the PID of this Info. */
  1971. char *
  1972. make_temp_filename (starter)
  1973.      char *starter;
  1974. {
  1975.   register int i;
  1976.   char *temp;
  1977.  
  1978.   temp = (char *) xmalloc (strlen (starter) + 10);
  1979.   sprintf (temp, "%s-%d", starter, getpid ());
  1980.  
  1981.   for (i = 0; temp[i]; i++)
  1982.     if (!isalnum (temp[i]))
  1983.       temp[i] = '-';
  1984.  
  1985.   return (temp);
  1986. }
  1987.  
  1988. /* Delete a file.  Print errors if necessary. */
  1989. int
  1990. deletefile (filename)
  1991.      char *filename;
  1992. {
  1993.   if (unlink (filename) != 0)
  1994.     {
  1995.       file_error (filename);
  1996.       return (1);
  1997.     }
  1998.   return (0);
  1999. }
  2000.  
  2001. int
  2002. printfile (filename)
  2003.      char *filename;
  2004. {
  2005.   /*
  2006.   int length = strlen (print_command) + strlen (filename) + strlen ("\n") + 1;
  2007.   */
  2008.   char *command = (char *) xmalloc (strlen (print_command) +
  2009.                     strlen (filename) + sizeof ("\n"));
  2010.   int error;
  2011.  
  2012.   display_error ("Printing file `%s'...\n", filename);
  2013.   sprintf (command, "%s %s", print_command, filename);
  2014.   error = system (command);
  2015.   if (error)
  2016.     display_error ("Can't invoke `%s'", command);
  2017.   free (command);
  2018.   return (error);
  2019. }
  2020.  
  2021. /* Dump the current node into a file named FILENAME.
  2022.    Return 0 if the dump was successful, otherwise,
  2023.    print error and exit. */
  2024. int
  2025. dump_current_node (filename)
  2026.      char *filename;
  2027. {
  2028.   size_t i = nodetop;
  2029.   int c;
  2030.   FILE *output_stream;
  2031.  
  2032.   if (strcmp (filename, "-") == 0)
  2033.     output_stream = stdout;
  2034.   else
  2035.     output_stream = fopen (filename, "w");
  2036.  
  2037.   if (output_stream == (FILE *) NULL)
  2038.     {
  2039.       file_error (filename);
  2040.       return (1);
  2041.     }
  2042.  
  2043.   while (i < nodebot && i < info_buffer_len)
  2044.     {
  2045.       c = info_file[i];
  2046.       if (CTRL_P (c) && !(index ("\n\t\f", c)))
  2047.     {
  2048.       putc ('^', output_stream);
  2049.       c = UNCTRL (c);
  2050.     }
  2051.  
  2052.       if (putc (c, output_stream) == EOF)
  2053.     {
  2054.       if (output_stream != stdout)
  2055.         fclose (output_stream);
  2056.  
  2057.       file_error (filename);
  2058.       return (1);
  2059.     }
  2060.       i++;
  2061.     }
  2062.  
  2063.   if (output_stream != stdout)
  2064.     fclose (output_stream);
  2065.  
  2066.   return (0);
  2067. }
  2068.  
  2069. /* **************************************************************** */
  2070. /*                                    */
  2071. /*             Toplevel eval loop.                 */
  2072. /*                                    */
  2073. /* **************************************************************** */
  2074.  
  2075. #define MENU_HEADER "\n* Menu:"
  2076. #define MENU_ID "\n* "
  2077. #define FOOTNOTE_HEADER "*Note"
  2078.  
  2079. /* Number of items that the current menu has. */
  2080. int the_menu_size = 0;
  2081.  
  2082. /* The node that last made a menus completion list. */
  2083. char menus_nodename[NODENAME_LEN];
  2084. char menus_filename[NODENAME_LEN];
  2085.  
  2086. static size_t search_start = 0;
  2087.  
  2088. /* The default prompt string for the Follow Reference command. */
  2089. char *visible_footnote = (char *)NULL;
  2090.  
  2091. void
  2092. toploop ()
  2093. {
  2094.   int done, inhibit_display;
  2095.   int command, last_command;
  2096.   size_t new_ypos, last_pagetop;
  2097.   char nodename[NODENAME_LEN];
  2098.  
  2099.   done = inhibit_display = 0;
  2100.   command = last_command = 0;
  2101.   new_ypos = last_pagetop = -1;
  2102.  
  2103.   while (!done)
  2104.     {
  2105.       if (!inhibit_display &&
  2106.       (window_bashed || (pagetop != last_pagetop)))
  2107.     display_page ();
  2108.  
  2109.       inhibit_display = window_bashed = 0;
  2110.       last_pagetop = pagetop;
  2111.  
  2112.       nodename[0] = '\0';    /* Don't display old text in input line. */
  2113.  
  2114.       last_command = command;
  2115.  
  2116.       if (last_command == 'S')
  2117.     cursor_to (search_start);
  2118.       else
  2119.     goto_xy (echo_area.left, echo_area.top);
  2120.  
  2121.       command = blink_cursor ();
  2122.       clear_echo_area ();
  2123.  
  2124.       if (command == EOF)
  2125.     {
  2126.       done = 1;
  2127.       continue;
  2128.     }
  2129.       command = to_upper (command);
  2130.       /* make CTRL('S') command synonymous with 'S' for searches */
  2131.       if (CTRL('S') == command)
  2132.     command = 'S';
  2133.  
  2134.       switch (command)
  2135.     {
  2136.     case 'D':
  2137.       get_node ((char *) NULL, "(dir)Top", 0);
  2138.       break;
  2139.  
  2140.     case 'H':
  2141.       if ((the_window.bottom - the_window.top) < 24)
  2142.         get_node ((char *) NULL, "(info)Help-Small-Screen", 0);
  2143.       else
  2144.         get_node ((char *) NULL, "(info)Help", 0);
  2145.       break;
  2146.  
  2147.     case 'N':
  2148.       if (!next_node ())
  2149.         {
  2150.           display_error ("No NEXT for this node!");
  2151.           inhibit_display = 1;
  2152.         }
  2153.       break;
  2154.  
  2155.     case 'P':
  2156.       if (!prev_node ())
  2157.         {
  2158.           display_error ("No PREV for this node!");
  2159.           inhibit_display = 1;
  2160.         }
  2161.       break;
  2162.  
  2163.     case 'U':
  2164.       {
  2165.         size_t savetop = pagetop;
  2166.  
  2167.         if (!up_node ())
  2168.           {
  2169.         display_error ("No UP for this node!");
  2170.         inhibit_display = 1;
  2171.         pagetop = savetop;
  2172.           }
  2173.         break;
  2174.       }
  2175.  
  2176.     case 'M':
  2177.       if (!build_menu ())
  2178.         {
  2179.           display_error ("No menu in this node!");
  2180.           inhibit_display = 1;
  2181.           break;
  2182.         }
  2183.  
  2184.       if (!readline ("Menu item: ", nodename, NODENAME_LEN, 1))
  2185.         {
  2186.           clear_echo_area ();
  2187.           inhibit_display = 1;
  2188.           break;
  2189.         }
  2190.  
  2191.       I_goto_xy (echo_area.left, echo_area.top);
  2192.       if (!find_menu_node (nodename, nodename))
  2193.         {
  2194.           display_error ("\"%s\" is not a menu item!", nodename);
  2195.           inhibit_display = 1;
  2196.           break;
  2197.         }
  2198.  
  2199.       if (get_node ((char *) NULL, nodename, 0))
  2200.         clear_echo_area ();
  2201.       break;
  2202.  
  2203.     case 'F':
  2204.       {
  2205.         char footnote[NODENAME_LEN];
  2206.  
  2207.         if (!build_notes ())
  2208.           {
  2209.         display_error ("No cross-references in this node!");
  2210.         inhibit_display = 1;
  2211.         break;
  2212.           }
  2213.  
  2214.         strcpy (footnote, visible_footnote);
  2215.         if (!readline ("Follow reference: ", footnote, NODENAME_LEN, 1))
  2216.           {
  2217.         inhibit_display = 1;
  2218.         break;
  2219.           }
  2220.  
  2221.         I_goto_xy (echo_area.left, echo_area.top);
  2222.         if (!find_note_node (footnote, nodename))
  2223.           {
  2224.         display_error ("\"%s\" is not a cross-reference in this node!",
  2225.                    footnote);
  2226.         inhibit_display = 1;
  2227.         break;
  2228.           }
  2229.  
  2230.         if (get_node ((char *)NULL, nodename, 0))
  2231.           clear_echo_area ();
  2232.         break;
  2233.       }
  2234.  
  2235.     case 'L':
  2236.       {
  2237.         char filename[FILENAME_LEN], nodename[NODENAME_LEN];
  2238.         size_t ptop, ntop;
  2239.         if (pop_node (filename, nodename, &ntop, &ptop) &&
  2240.         get_node (filename, nodename, 1))
  2241.           {
  2242.         pagetop = ptop;
  2243.           }
  2244.         else
  2245.           inhibit_display = 1;
  2246.         break;
  2247.       }
  2248.  
  2249.     case SPACE:
  2250.     case CTRL ('V'):
  2251.       if (!next_page ())
  2252.         {
  2253.           display_error ("At last page of this node now!");
  2254.           inhibit_display = 1;
  2255.         }
  2256.       break;
  2257.  
  2258.     case META ('V'):
  2259.     case DELETE:
  2260.       if (!prev_page ())
  2261.         {
  2262.           display_error ("At first page of this node now!");
  2263.           inhibit_display = 1;
  2264.         }
  2265.       break;
  2266.  
  2267.     case 'B':
  2268.       if (pagetop == nodetop)
  2269.         {
  2270.           display_error ("Already at beginning of this node!");
  2271.           inhibit_display = 1;
  2272.         }
  2273.       else
  2274.         pagetop = nodetop;
  2275.       break;
  2276.  
  2277.       /* I don't want to do this this way, but the documentation
  2278.          clearly states that '6' doesn't work.  It states this for a
  2279.          reason, and ours is not to wonder why... */
  2280.     case '1':
  2281.     case '2':
  2282.     case '3':
  2283.     case '4':
  2284.     case '5':
  2285.       {
  2286.         int item = command - '0';
  2287.  
  2288.         if (!build_menu ())
  2289.           {
  2290.         display_error ("No menu in this node!");
  2291.         inhibit_display = 1;
  2292.         break;
  2293.           }
  2294.  
  2295.         if (item > the_menu_size)
  2296.           {
  2297.         display_error ("There are only %d items in the menu!",
  2298.                    the_menu_size);
  2299.         inhibit_display = 1;
  2300.         break;
  2301.           }
  2302.  
  2303.         if (!get_menu (item))
  2304.           inhibit_display = 1;
  2305.       }
  2306.       break;
  2307.  
  2308.     case 'G':
  2309.       if (!readline ("Goto node: ", nodename, NODENAME_LEN, 0))
  2310.         {
  2311.           inhibit_display = 1;
  2312.           break;
  2313.         }
  2314.  
  2315.       if (get_node ((char *) NULL, nodename, 0))
  2316.         clear_echo_area ();
  2317.       break;
  2318.  
  2319.       /* Search from the starting position forward for a string.
  2320.          Select the node containing the desired string.  Put the
  2321.          top of the page screen_lines / 2 lines behind it, but not
  2322.          before nodetop. */
  2323.     case 'S':
  2324.       {
  2325.         size_t pointer, temp;
  2326.          char prompt[21 + NODENAME_LEN + 1];
  2327.          static char search_string[NODENAME_LEN] = "";
  2328.          static char *starting_filename = NULL,
  2329.                 *starting_nodename = NULL;
  2330.         static size_t starting_pagetop = 0;
  2331.         static int wrap_search = 0;
  2332.  
  2333.          sprintf (prompt, "Search for string [%s]: ", search_string);
  2334.  
  2335.          if (!readline (prompt , nodename, NODENAME_LEN, 0))
  2336.           {
  2337.         inhibit_display = 1;
  2338.         break;
  2339.           }
  2340.  
  2341.         /* If the user defaulted the search string, and the previous
  2342.            command was search, then this is a continuation of the
  2343.            previous search. */
  2344.         if (((strcmp (nodename, search_string) == 0) ||
  2345.          (!*nodename && *search_string)) &&
  2346.         (last_command == 'S'))
  2347.           {
  2348.         search_start++;
  2349.           }
  2350.         else
  2351.           {
  2352.         /* Initialize the start of a new search. */
  2353.         if (starting_filename)
  2354.           free (starting_filename);
  2355.  
  2356.         starting_filename = savestring (last_loaded_info_file);
  2357.  
  2358.         if (starting_nodename)
  2359.           free (starting_nodename);
  2360.  
  2361.         starting_nodename = savestring (nodename ? nodename : "");
  2362.  
  2363.         starting_pagetop = pagetop;
  2364.         search_start = pagetop;
  2365.         wrap_search = 0;
  2366.  
  2367.         if (*nodename != '\0')
  2368.           strcpy (search_string, nodename);
  2369.           }
  2370.  
  2371.         I_goto_xy (echo_area.left, echo_area.top);
  2372.  
  2373.         {
  2374.           static int pushed = 0; /* How many files are pushed? */
  2375.           int found_string = 0;  /* Did we find our string? */
  2376.  
  2377.           if (wrap_search)
  2378.         {
  2379.           push_filestack (next_info_file ((char *)NULL, 1), 0);
  2380.           pushed++;
  2381.           search_start = 0;
  2382.         }
  2383.  
  2384.           for (;;)
  2385.         {
  2386.           set_search_constraints (info_file, info_buffer_len);
  2387.           pointer = search_forward (search_string, search_start);
  2388.  
  2389.           if (pointer != -1)
  2390.             {
  2391.               found_string = 1;
  2392.               break;
  2393.             }
  2394.           else
  2395.             {
  2396.               char *next_file;
  2397.  
  2398.               next_file = next_info_file ((char *)NULL, 0);
  2399.  
  2400.               if (next_file != NULL)
  2401.             {
  2402.               if (pushed)
  2403.                 {
  2404.                   pop_filestack ();
  2405.                   pushed--;
  2406.                 }
  2407.  
  2408.               push_filestack (next_file, 0);
  2409.               pushed++;
  2410.               search_start = 0;
  2411. #if 1
  2412.               I_goto_xy (echo_area.left, echo_area.top);
  2413.               print_string ("Searching file %s...\n", next_file);
  2414. #endif
  2415.               continue;
  2416.             }
  2417.  
  2418.               if (wrap_search)
  2419.             {
  2420.               display_error
  2421.                 ("\"%s\" not found!",
  2422.                  search_string);
  2423.  
  2424.               inhibit_display = 1;
  2425.               wrap_search = 0;
  2426.  
  2427.               if (pushed)
  2428.                 {
  2429.                   pop_filestack ();
  2430.                   pushed--;
  2431.                 }
  2432.               break;
  2433.             }
  2434.               else
  2435.             {
  2436.               display_error ("Search: End of file");
  2437.               inhibit_display = 1;
  2438.               wrap_search = 1;
  2439.  
  2440.               if (pushed)
  2441.                 {
  2442.                   pop_filestack ();
  2443.                   pushed--;
  2444.                 }
  2445.               break;
  2446.             }
  2447.             }
  2448.         }
  2449.  
  2450.           if (pushed)
  2451.         {
  2452.           swap_filestack ();
  2453.           pop_filestack ();
  2454.           pushed--;
  2455.         }
  2456.  
  2457.           if (!found_string)
  2458.         break;
  2459.  
  2460.           wrap_search = 0;
  2461.           temp = search_backward (start_of_node_string, pointer);
  2462.  
  2463.           if (temp != -1)
  2464.          {
  2465.           search_start = pointer;
  2466.           pointer = forward_lines (1, temp);
  2467.         }
  2468.  
  2469.           if (temp == -1 || !extract_field ("Node:", nodename, pointer))
  2470.         {
  2471.           display_error
  2472.             ("There doesn't appear to be a nodename for this node.");
  2473.           get_node ((char *)NULL, "*", 0);
  2474.           pagetop = pointer;
  2475.           break;
  2476.         }
  2477.          
  2478.           /* Get the node if it is different than the one already
  2479.          loaded. */
  2480.           if (strcmp (nodename, starting_nodename) != 0)
  2481.         {
  2482.           free (starting_nodename);
  2483.           starting_nodename = savestring (nodename);
  2484.  
  2485.           if (get_node ((char *) NULL, nodename, 0))
  2486.             clear_echo_area ();
  2487.         }
  2488.          
  2489.           /* Reset the top of page if necessary. */
  2490.           {
  2491.         if ((strcmp (last_loaded_info_file, starting_filename) != 0) ||
  2492.             (starting_pagetop != pagetop) ||
  2493.             (search_start > pagebot))
  2494.           {
  2495.             pointer =
  2496.               back_lines ((the_window.bottom - the_window.top) / 2,
  2497.                   forward_lines (1, search_start));
  2498.  
  2499.             if (pointer < nodetop)
  2500.               pointer = nodetop;
  2501.            
  2502.             pagetop = pointer;
  2503.             window_bashed = 1;
  2504.           }
  2505.         else
  2506.           inhibit_display = 1;
  2507.           }
  2508.           break;
  2509.         }
  2510.          }
  2511.  
  2512.     case CTRL ('H'):
  2513.     case '?':
  2514.       help_use_info ();
  2515.       last_pagetop = -1;
  2516.       break;
  2517.  
  2518.     case 'Q':
  2519.       done = 1;
  2520.       break;
  2521.  
  2522.     case CTRL ('L'):    /* Control-l is redisplay. */
  2523.       window_bashed = 1;
  2524.       if (last_command == 'S')
  2525.         command = 'S';
  2526.       break;
  2527.  
  2528.     case '(':    /* You *must* be trying to type a complete nodename. */
  2529.       strcpy (nodename, "(");
  2530.       if (!readline ("Goto node: ", nodename, NODENAME_LEN, 0))
  2531.         {
  2532.           inhibit_display = 1;
  2533.           clear_echo_area ();
  2534.           break;
  2535.         }
  2536.       I_goto_xy (echo_area.left, echo_area.top);
  2537.       if (get_node ((char *) NULL, nodename, 0))
  2538.         clear_echo_area ();
  2539.       break;
  2540.  
  2541.     case CTRL ('P'):
  2542.       /* Print the contents of this node on the default printer.  We
  2543.          would like to let the user specify the printer, but we don't
  2544.          want to ask her each time which printer to use.  Besides, he
  2545.          might not know, which is why it (the user) is in the need of
  2546.          Info. */
  2547.       {
  2548.         char *tempname = make_temp_filename (current_info_node);
  2549.         if (dump_current_node (tempname) == 0 &&
  2550.         printfile (tempname) == 0 &&
  2551.         deletefile (tempname) == 0)
  2552.           {
  2553.         display_error ("Printed node.  Go pick up your output.\n");
  2554.           }
  2555.         inhibit_display = 1;
  2556.         free (tempname);
  2557.       }
  2558.       break;
  2559.  
  2560.     default:
  2561.       inhibit_display = 1;
  2562.       display_error ("Unknown command! Press '?' for help.");
  2563.  
  2564.     }
  2565.     }
  2566. }
  2567.  
  2568. /* Return the screen column width that the line from START to END
  2569.    requires to display. */
  2570. int
  2571. line_length (start, end)
  2572.      size_t start, end;
  2573. {
  2574.   int count = 0;
  2575.  
  2576.   while (start < end)
  2577.     {
  2578.       if (info_file[start] == '\t')
  2579.     count += 7 - (count % 8);
  2580.       else if (CTRL_P (info_file[start]))
  2581.     count += 2;
  2582.       else
  2583.     count++;
  2584.  
  2585.       start++;
  2586.     }
  2587.  
  2588.   return (count);
  2589. }
  2590.  
  2591. /* Tell this person how to use Info. */
  2592. void
  2593. help_use_info ()
  2594. {
  2595.   open_typeout ();
  2596.   clear_screen ();
  2597.   print_string ("\n\
  2598.           Commands in Info\n\
  2599. \n\
  2600. h    Invoke the Info tutorial.\n\
  2601. \n\
  2602. Selecting other nodes:\n\
  2603. n    Move to the \"next\" node of this node.\n\
  2604. p    Move to the \"previous\" node of this node.\n\
  2605. u    Move \"up\" from this node.\n\
  2606. m    Pick menu item specified by name.\n\
  2607.     Picking a menu item causes another node to be selected.\n\
  2608. f    Follow a cross reference.  Reads name of reference.\n\
  2609. l    Move to the last node you were at.\n\
  2610. d    Move to the `directory' node.  Equivalent to `gDIR'.\n\
  2611. \n\
  2612. Moving within a node:\n\
  2613. Space    Scroll forward a page.\n\
  2614. DEL    Scroll backward a page.\n\
  2615. b    Go to the beginning of this node.\n\
  2616. \n\
  2617. Advanced commands:\n\
  2618. q    Quit Info.\n\
  2619. 1    Pick first item in node's menu.\n\
  2620. 2 - 5   Pick second ... fifth item in node's menu.\n\
  2621. g    Move to node specified by name.\n\
  2622.     You may include a filename as well, as (FILENAME)NODENAME.\n\
  2623. s    Search through this Info file for a specified string,\n\
  2624.     and select the node in which the next occurrence is found.\n\
  2625. Ctl-p   Print the contents of this node using `%s'.\n\
  2626. \n\
  2627. Done.\n\n",print_command);
  2628.   close_typeout ();
  2629. }
  2630.  
  2631. /* Move to the node specified in the NEXT field. */
  2632. int
  2633. next_node ()
  2634. {
  2635.   char nodename[NODENAME_LEN];
  2636.  
  2637.   if (!extract_field ("Next:", nodename, nodetop))
  2638.     return (0);
  2639.   return (get_node ((char *) NULL, nodename, 0));
  2640. }
  2641.  
  2642. /* Move to the node specified in the PREVIOUS field. */
  2643. int
  2644. prev_node ()
  2645. {
  2646.   char nodename[NODENAME_LEN];
  2647.  
  2648.   if (!extract_field ("Previous:", nodename, nodetop)
  2649.       && !extract_field ("Prev:", nodename, nodetop))
  2650.     return (0);
  2651.   return (get_node ((char *) NULL, nodename, 0));
  2652. }
  2653.  
  2654. /* Move to the node specified in the UP field. */
  2655. int
  2656. up_node ()
  2657. {
  2658.   char nodename[NODENAME_LEN];
  2659.  
  2660.   if (!extract_field ("Up:", nodename, nodetop))
  2661.     return (0);
  2662.   return (get_node ((char *) NULL, nodename, 0));
  2663. }
  2664.  
  2665. /* Build a completion list of menuname/nodename for each
  2666.    line in this node that is a menu item. */
  2667. int
  2668. build_menu ()
  2669. {
  2670.   size_t pointer = nodetop;
  2671.   char menuname[NODENAME_LEN];
  2672.   char nodename[NODENAME_LEN];
  2673.  
  2674.   if (strcmp (menus_nodename, current_info_node) == 0 &&
  2675.       strcmp (menus_filename, current_info_file) == 0)
  2676.     return (the_menu_size != 0);
  2677.  
  2678.   strcpy (menus_nodename, current_info_node);
  2679.   strcpy (menus_filename, current_info_file);
  2680.   free_completion_list ();
  2681.   the_menu_size = 0;
  2682.  
  2683.   set_search_constraints (info_file, nodebot);
  2684.   if ((long)(pointer = search_forward (MENU_HEADER, nodetop)) < 0)
  2685.     return (0);
  2686.  
  2687.   /* There is a menu here.  Look for members of it. */
  2688.   pointer += strlen (MENU_HEADER);
  2689.  
  2690.   while (1)
  2691.     {
  2692.       int idx;
  2693.  
  2694.       pointer = search_forward (MENU_ID, pointer);
  2695.       if ((long) pointer < 0)
  2696.     break;            /* No more menus in this node. */
  2697.  
  2698.       pointer = (skip_whitespace (pointer + strlen (MENU_ID)));
  2699.  
  2700.       idx = 0;
  2701.       while ((menuname[idx] = info_file[pointer]) && menuname[idx] != ':')
  2702.     idx++, pointer++;
  2703.  
  2704.       menuname[idx] = '\0';
  2705.       pointer++;
  2706.  
  2707.       if (info_file[pointer] == ':')
  2708.     {
  2709.       strcpy (nodename, menuname);
  2710.     }
  2711.       else
  2712.     {
  2713.       int in_parens;
  2714.  
  2715.       pointer = skip_whitespace (pointer);
  2716.       idx = in_parens = 0;
  2717.  
  2718.       while ((nodename[idx] = info_file[pointer]) &&
  2719.          (in_parens ||
  2720.           (nodename[idx] != '\t' &&
  2721.            nodename[idx] != '.' &&
  2722.            nodename[idx] != ',')))
  2723.         {
  2724.           if (nodename[idx] == '(')
  2725.         in_parens++;
  2726.           else if (nodename[idx] == ')')
  2727.         in_parens--;
  2728.  
  2729.           idx++, pointer++;
  2730.         }
  2731.       nodename[idx] = '\0';
  2732.     }
  2733.       add_completion (menuname, nodename);
  2734.       the_menu_size++;
  2735.     }
  2736.   if (the_menu_size)
  2737.     completion_list = reverse_list (completion_list);
  2738.   return (the_menu_size != 0);
  2739. }
  2740.  
  2741. /* Select ITEMth item from a list built by build_menu (). */
  2742. int
  2743. get_menu (item)
  2744.      int item;
  2745. {
  2746.   if (!build_menu ())
  2747.     return (0);
  2748.  
  2749.   if (item > the_menu_size)
  2750.     return (0);
  2751.   else
  2752.     {
  2753.       COMP_ENTRY *temp = completion_list;
  2754.  
  2755.       while (--item && temp)
  2756.     temp = temp->next;
  2757.  
  2758.       return (get_node ((char *) NULL, temp->data, 0));
  2759.     }
  2760. }
  2761.  
  2762. /* Scan through the ?already? built menu list looking
  2763.    for STRING.  If you find it, put the corresponding nodes
  2764.    name in NODENAME. */
  2765. int
  2766. find_menu_node (string, nodename)
  2767.      char *string, *nodename;
  2768. {
  2769.   return (scan_list (string, nodename));
  2770. }
  2771.  
  2772. /* The work part of find_menu_node and find_note_node. */
  2773. int
  2774. scan_list (string, nodename)
  2775.      char *string, *nodename;
  2776. {
  2777.   COMP_ENTRY *temp = completion_list;
  2778.  
  2779.   while (temp)
  2780.     {
  2781.       if (strnicmp (string, temp->identifier, strlen (string)) == 0)
  2782.     {
  2783.       strcpy (nodename, temp->data);
  2784.       return (1);
  2785.     }
  2786.       temp = temp->next;
  2787.     }
  2788.   return (0);
  2789. }
  2790.  
  2791. /* Remove <CR> and whitespace from string, replacing them with
  2792.    only one space.  Exception:  <CR> at end of string disappears. */
  2793. void
  2794. clean_up (string)
  2795.      char *string;
  2796. {
  2797.   char *to;
  2798.  
  2799.   /* Skip all whitespace characters found at the start of STRING. */
  2800.   while (whitespace (*string))
  2801.     string++;
  2802.  
  2803.   to = string;
  2804.  
  2805.   while (*to = *string++)
  2806.     {
  2807.       if (*to == '\n' || *to == ' ')
  2808.     {
  2809.       *to = ' ';
  2810.  
  2811.       while (*string == ' ' || *string == '\t')
  2812.         string++;
  2813.     }
  2814.     to++;
  2815.     }
  2816. }
  2817.  
  2818. /* Find a reference to "*Note".  Return the offset of the start
  2819.    of that reference, or -1. */
  2820. size_t
  2821. find_footnote_ref (from)
  2822.      size_t from;
  2823. {
  2824.   while (1)
  2825.     {
  2826.       from = search_forward (FOOTNOTE_HEADER, from);
  2827.       if ((long)from < 0)
  2828.     return (from);
  2829.       else
  2830.     from += strlen (FOOTNOTE_HEADER);
  2831.       if (info_file[from] == ' ' ||
  2832.       info_file[from] == '\n' ||
  2833.       info_file[from] == '\t')
  2834.     return (from);
  2835.     }
  2836. }
  2837.  
  2838. /* Build an array of (footnote.nodename) for each footnote in this node. */
  2839. int
  2840. build_notes ()
  2841. {
  2842.   size_t pointer;
  2843.   char notename[NODENAME_LEN];
  2844.   char nodename[NODENAME_LEN];
  2845.  
  2846.   set_search_constraints (info_file, nodebot);
  2847.  
  2848.   if ((long)(find_footnote_ref (nodetop)) < 0)
  2849.     return (0);
  2850.   pointer = nodetop;
  2851.  
  2852.   menus_filename[0] = menus_nodename[0] = '\0';
  2853.   visible_footnote = "";
  2854.   free_completion_list ();
  2855.  
  2856.   while (1)
  2857.     {
  2858.       int idx;
  2859.  
  2860.       pointer = find_footnote_ref (pointer);
  2861.       if ((long)pointer < 0)
  2862.     break;            /* no more footnotes in this node. */
  2863.  
  2864.       pointer = skip_whitespace_and_cr (pointer);
  2865.       idx = 0;
  2866.  
  2867.       while ((notename[idx] = info_file[pointer]) && notename[idx] != ':')
  2868.     {
  2869.       idx++, pointer++;
  2870.     }
  2871.  
  2872.       notename[idx] = '\0';
  2873.       clean_up (notename);
  2874.       pointer++;
  2875.       if (info_file[pointer] == ':')
  2876.     {
  2877.       strcpy (nodename, notename);
  2878.     }
  2879.       else
  2880.     {
  2881.       int in_parens = 0;
  2882.  
  2883.       pointer = skip_whitespace (pointer);
  2884.       idx = 0;
  2885.  
  2886.       while ((nodename[idx] = info_file[pointer]) &&
  2887.          (in_parens ||
  2888.           (nodename[idx] != '\t' &&
  2889.            nodename[idx] != '.' &&
  2890.            nodename[idx] != ',')))
  2891.         {
  2892.           if (nodename[idx] == '(')
  2893.         in_parens++;
  2894.           else if (nodename[idx] == ')')
  2895.         in_parens--;
  2896.  
  2897.           idx++, pointer++;
  2898.         }
  2899.       nodename[idx] = '\0';
  2900.       clean_up (nodename);
  2901.     }
  2902.       /* Add the notename/nodename to the list. */
  2903.       add_completion (notename, nodename);
  2904.       the_menu_size++;
  2905.  
  2906.       /* Remember this identifier as the default if it is the first one in the
  2907.          page. */
  2908.       if (!(*visible_footnote) &&
  2909.       pointer > pagetop &&
  2910.       pointer < forward_lines (the_window.bottom - the_window.top, pointer))
  2911.     visible_footnote = completion_list->identifier;
  2912.     }
  2913.   if (the_menu_size)
  2914.     completion_list = reverse_list (completion_list);
  2915.   return (the_menu_size != 0);
  2916. }
  2917.  
  2918. /* Scan through the ?already? built footnote list looking for STRING.
  2919.    If found, place the corresponding node name in NODENAME. */
  2920. int
  2921. find_note_node (string, nodename)
  2922.      char *string, *nodename;
  2923. {
  2924.   return (scan_list (string, nodename));
  2925. }
  2926.  
  2927. /* **************************************************************** */
  2928. /*                                    */
  2929. /*            Page Display                     */
  2930. /*                                    */
  2931. /* **************************************************************** */
  2932.  
  2933.  
  2934. /* The display functions for GNU Info. */
  2935. int display_ch, display_cv;
  2936. size_t display_point;
  2937.  
  2938. /* Display the current page from pagetop down to the bottom of the
  2939.    page or the bottom of the node, whichever comes first. */
  2940. void
  2941. display_page ()
  2942. {
  2943.   display_point = pagetop;
  2944.   display_ch = the_window.left;
  2945.   display_cv = the_window.top;
  2946.   generic_page_display ();
  2947. }
  2948.  
  2949. /* Print the page from display_point to bottom of node, or window,
  2950.    whichever comes first.  Start printing at display_ch, display_cv. */
  2951. void
  2952. generic_page_display ()
  2953. {
  2954.   int done_with_display = 0;
  2955.   int character;
  2956.  
  2957.   goto_xy (display_ch, display_cv);
  2958.  
  2959.   while (!done_with_display)
  2960.     {
  2961.       if (display_point == nodebot)
  2962.     {
  2963.       clear_eop ();
  2964.       goto display_finish;
  2965.     }
  2966.  
  2967.       character = info_file[display_point];
  2968.  
  2969.       if ((display_width (character, the_window.ch) + the_window.ch)
  2970.       >= the_window.right)
  2971.     display_carefully (character);
  2972.       else
  2973.     charout (character);
  2974.  
  2975.       if ((the_window.cv >= the_window.bottom)
  2976.       || (the_window.cv == the_window.top
  2977.           && the_window.ch == the_window.left))
  2978.     {
  2979.     display_finish:
  2980.       pagebot = display_point;
  2981.       make_modeline ();
  2982.       done_with_display++;
  2983.       continue;
  2984.     }
  2985.       else
  2986.     display_point++;
  2987.     }
  2988.   fflush (stdout);
  2989. }
  2990.  
  2991. /* Display character carefully, ensuring that no scrolling takes
  2992.    place, even in the case of funky control characters. */
  2993. void
  2994. display_carefully (character)
  2995.      int character;
  2996. {
  2997.   if (CTRL_P (character))
  2998.     {
  2999.       switch (character)
  3000.     {
  3001.     case RETURN:
  3002.     case NEWLINE:
  3003.     case TAB:
  3004.       clear_eol ();
  3005.       advance (the_window.right - the_window.ch);
  3006.       break;
  3007.     default:
  3008.       charout ('^');
  3009.       if (the_window.cv == the_window.bottom)
  3010.         break;
  3011.       else
  3012.         charout (UNCTRL (character));
  3013.     }
  3014.     }
  3015.   else
  3016.     charout (character);
  3017. }
  3018.  
  3019. /* Move the cursor to POSITION in page.  Return non-zero if successful. */
  3020. int
  3021. cursor_to (position)
  3022.      size_t position;
  3023. {
  3024.   int ch, cv, character;
  3025.   size_t point;
  3026.  
  3027.   if (position > pagebot || position < pagetop)
  3028.     return (0);
  3029.  
  3030.   point = pagetop;
  3031.   ch = the_window.left;
  3032.   cv = the_window.top;
  3033.  
  3034.   while (point < position)
  3035.     {
  3036.       character = info_file[point++];
  3037.  
  3038.       ch += display_width (character, ch);
  3039.  
  3040.       if (ch >= the_window.right)
  3041.     {
  3042.       ch = ch - the_window.right;
  3043.       cv++;
  3044.  
  3045.       if (cv >= the_window.bottom)
  3046.         return (0);
  3047.     }
  3048.     }
  3049.   goto_xy (ch, cv);
  3050.   return (1);
  3051. }
  3052.  
  3053. /* Move to the next page in this node.  Return 0 if
  3054.    we can't get to the next page. */
  3055. int
  3056. next_page ()
  3057. {
  3058.   size_t pointer;
  3059.  
  3060.   pointer =
  3061.     forward_lines ((the_window.bottom - the_window.top) - 2, pagetop);
  3062.  
  3063.   if (pointer >= nodebot)
  3064.     return (0);
  3065.  
  3066.   /* Hack for screens smaller than displayed line width. */
  3067.   if (pointer > display_point)
  3068.     {
  3069.       pointer = display_point;
  3070.       back_lines (1, pointer);
  3071.     }
  3072.   pagetop = pointer;
  3073.   return (1);
  3074. }
  3075.  
  3076. /* Move to the previous page in this node.  Return zero if
  3077.    there is no previous page. */
  3078. int
  3079. prev_page ()
  3080. {
  3081.   size_t pointer =
  3082.   back_lines ((the_window.bottom - the_window.top) - 2, pagetop);
  3083.  
  3084.   if (pagetop == nodetop)
  3085.     return (0);
  3086.  
  3087.   if (pointer < nodetop)
  3088.     pointer = nodetop;
  3089.  
  3090.   pagetop = pointer;
  3091.   return (1);
  3092. }
  3093.  
  3094.  
  3095. /* **************************************************************** */
  3096. /*                                    */
  3097. /*            Utility Functions                */
  3098. /*                                    */
  3099. /* **************************************************************** */
  3100.  
  3101. char *search_buffer;        /* area in ram to scan through. */
  3102. size_t buffer_bottom;        /* Length of this area. */
  3103.  
  3104. /* Set the global variables that all of these routines use. */
  3105. void
  3106. set_search_constraints (buffer, extent)
  3107.      char *buffer;
  3108.      size_t extent;
  3109. {
  3110.   search_buffer = buffer;
  3111.   buffer_bottom = extent;
  3112. }
  3113.  
  3114. /* Move back to the start of this line. */
  3115. size_t
  3116. to_beg_line (from)
  3117.      size_t from;
  3118. {
  3119.   while (from && search_buffer[from - 1] != '\n')
  3120.     from--;
  3121.   return (from);
  3122. }
  3123.  
  3124. /* Move forward to the end of this line. */
  3125. size_t
  3126. to_end_line (from)
  3127.      size_t from;
  3128. {
  3129.   while (from < buffer_bottom && search_buffer[from] != '\n')
  3130.     from++;
  3131.   return (from);
  3132. }
  3133.  
  3134. /* Move back count lines in search_buffer starting at starting_pos.
  3135.    Returns the start of that line. */
  3136. size_t
  3137. back_lines (count, starting_pos)
  3138.      size_t count, starting_pos;
  3139. {
  3140.   starting_pos = to_beg_line (starting_pos);
  3141.   while (starting_pos && count)
  3142.     {
  3143.       starting_pos = to_beg_line (starting_pos - 1);
  3144.       count--;
  3145.     }
  3146.   return (starting_pos);
  3147. }
  3148.  
  3149. /* Move forward count lines starting at starting_pos.
  3150.    Returns the start of that line. */
  3151. size_t
  3152. forward_lines (count, starting_pos)
  3153.      size_t count, starting_pos;
  3154. {
  3155.   starting_pos = to_end_line (starting_pos);
  3156.   while (starting_pos < buffer_bottom && count)
  3157.     {
  3158.       starting_pos = to_end_line (starting_pos + 1);
  3159.       count--;
  3160.     }
  3161.   return (to_beg_line (starting_pos));
  3162. }
  3163.  
  3164. /* Search for STRING in SEARCH_BUFFER starting at STARTING_POS.
  3165.    Return the location of the string, or -1 if not found. */
  3166. size_t
  3167. search_forward (string, starting_pos)
  3168.      const char *string;
  3169.      size_t starting_pos;
  3170. {
  3171.   register int c, i, len;
  3172.   register char *buff, *end;
  3173.   char *alternate;
  3174.  
  3175.  
  3176.   /* We match characters in SEARCH_BUFFER against STRING and ALTERNATE.
  3177.      ALTERNATE is a case reversed version of STRING; this is cheaper than
  3178.      case folding each character before comparison. */
  3179.  
  3180.   /* Build the alternate string. */
  3181.   alternate = savestring (string);
  3182.   len = (int)strlen (string);
  3183.  
  3184.   for (i = 0; i < len; i++)
  3185.     {
  3186.       c = alternate[i];
  3187.  
  3188.       if (c >= 'a' && c <= 'z')
  3189.     alternate[i] = c - 32;
  3190.       else if (c >= 'A' && c <= 'Z')
  3191.     alternate[i] = c + 32;
  3192.     }
  3193.  
  3194.   buff = search_buffer + starting_pos;
  3195.   end = search_buffer + buffer_bottom + 1;
  3196.  
  3197.   while (buff < end)
  3198.     {
  3199.       for (i = 0; i < len; i++)
  3200.     {
  3201.       c = buff[i];
  3202.  
  3203.       if (c != string[i] && c != alternate[i])
  3204.         break;
  3205.     }
  3206.  
  3207.       if (!string[i])
  3208.     {
  3209.       free (alternate);
  3210.       return (buff - search_buffer);
  3211.     }
  3212.  
  3213.       buff++;
  3214.     }
  3215.  
  3216.   free (alternate);
  3217.   return (-1);
  3218. }
  3219.  
  3220. /* Search for STRING in SEARCH_BUFFER starting at STARTING_POS.
  3221.    Return the location of the string, or -1 if not found. */
  3222. size_t
  3223. search_backward (string, starting_pos)
  3224.      const char *string;
  3225.      size_t starting_pos;
  3226. {
  3227.   size_t len = strlen (string);
  3228.   while ((long)(starting_pos - len) > -1)
  3229.     {
  3230.       if (strnicmp (search_buffer + (starting_pos - len), string, len) == 0)
  3231.     return (starting_pos - len);
  3232.       else
  3233.     starting_pos--;
  3234.     }
  3235.   return (-1);
  3236. }
  3237.  
  3238. /* Only search for STRING from POINTER to end of line.  Return offset
  3239.    of string, or -1 if not found. */
  3240. size_t
  3241. string_in_line (string, pointer)
  3242.      const char *string;
  3243.      size_t pointer;
  3244. {
  3245.   size_t old_buffer_bottom = buffer_bottom;
  3246.  
  3247.   set_search_constraints (search_buffer, to_end_line (pointer));
  3248.   pointer = search_forward (string, pointer);
  3249.   buffer_bottom = old_buffer_bottom;
  3250.   return (pointer);
  3251. }
  3252.  
  3253. /* Skip whitespace characters at OFFSET in SEARCH_BUFFER.
  3254.    Return the next non-whitespace character or -1 if BUFFER_BOTTOM
  3255.    is reached. */
  3256. size_t
  3257. skip_whitespace (offset)
  3258.      size_t offset;
  3259. {
  3260.   int character;
  3261.  
  3262.   while (offset < buffer_bottom)
  3263.     {
  3264.       character = search_buffer[offset];
  3265.       if (character == ' ' || character == '\t')
  3266.     offset++;
  3267.       else
  3268.     return (offset);
  3269.     }
  3270.   return (-1);
  3271. }
  3272.  
  3273. /* Skip whitespace characters including <CR> at OFFSET in
  3274.    SEARCH_BUFFER.  Return the position of the next non-whitespace
  3275.    character, or -1 if BUFFER_BOTTOM is reached. */
  3276. size_t
  3277. skip_whitespace_and_cr (offset)
  3278.      size_t offset;
  3279. {
  3280.   while (1)
  3281.     {
  3282.       offset = skip_whitespace (offset);
  3283.       if ((long) offset > 0 && search_buffer[offset] != '\n')
  3284.         break;
  3285.       else
  3286.     offset++;
  3287.     }
  3288.     return (offset);
  3289. }
  3290.  
  3291. /* Extract the node name part of the of the text after the FIELD.
  3292.    Place the node name into NODENAME.  Assume the line starts at
  3293.    OFFSET in SEARCH_BUFFER. */
  3294. int
  3295. extract_field (field_name, nodename, offset)
  3296.      const char *field_name;
  3297.      char *nodename;
  3298.      size_t offset;
  3299. {
  3300.   size_t temp;
  3301.   int character;
  3302.  
  3303.   temp = string_in_line (field_name, offset);
  3304.   if ((long)temp < 0)
  3305.     return (0);
  3306.  
  3307.   temp += strlen (field_name);
  3308.   temp = skip_whitespace (temp);
  3309.  
  3310.   /* Okay, place the following text into NODENAME. */
  3311.  
  3312.   while ((character = search_buffer[temp]) != ','
  3313.      && character != '\n'
  3314.      && character != '\t')
  3315.     {
  3316.       *nodename = character;
  3317.       nodename++;
  3318.       temp++;
  3319.     }
  3320.   *nodename = '\0';
  3321.   return (1);
  3322. }
  3323.  
  3324. /* Return non-zero if pointer is exactly at string, else zero. */
  3325. int
  3326. looking_at (string, pointer)
  3327.      const char *string;
  3328.      size_t pointer;
  3329. {
  3330.   if (strnicmp (search_buffer + pointer, string, strlen (string)) == 0)
  3331.     return (1);
  3332.   else
  3333.     return (0);
  3334. }
  3335.  
  3336. /* File stack stuff. This is currently only used to push one file while
  3337.    searching indirect files, but we may as well write it in full
  3338.    generality. */
  3339. typedef struct filestack
  3340. {
  3341.   struct filestack *next;
  3342.   char filename[FILENAME_LEN];
  3343.   char current_filename[FILENAME_LEN];
  3344.   char *tag_table;
  3345.   char *info_file;
  3346.   size_t info_buffer_len;
  3347. } FILESTACK;
  3348.  
  3349. FILESTACK *filestack = NULL;
  3350.  
  3351. int
  3352. push_filestack (filename, remember_name)
  3353.      char *filename;
  3354.      int remember_name;
  3355. {
  3356.   FILESTACK *element = (FILESTACK *) xmalloc (sizeof (FILESTACK));
  3357.  
  3358.   element->next = filestack;
  3359.   filestack = element;
  3360.  
  3361.   strcpy (filestack->filename, last_loaded_info_file);
  3362.   strcpy (filestack->current_filename, current_info_file);
  3363.   filestack->tag_table = tag_table;
  3364.   filestack->info_file = info_file;
  3365.   filestack->info_buffer_len = info_buffer_len;
  3366.   
  3367.   *last_loaded_info_file = '\0';    /* force the file to be read */
  3368.   info_file = (char *)NULL;    /* Pretend we have no buffer. */
  3369.   if (get_info_file (filename, remember_name))
  3370.     {
  3371.       return (1);
  3372.     }
  3373.   else
  3374.     {
  3375.       pop_filestack ();
  3376.       return (0);
  3377.     }
  3378. }
  3379.  
  3380. void
  3381. pop_filestack ()
  3382. {
  3383.   FILESTACK *temp;
  3384.    
  3385.   if (filestack == NULL)
  3386.     {
  3387.        fprintf (stderr , "File stack is empty and can't be popped\n");
  3388.        brians_error ();
  3389.        return;
  3390.     }
  3391.  
  3392.   free (info_file);
  3393.  
  3394.   strcpy (last_loaded_info_file, filestack->filename);
  3395.   strcpy (current_info_file, filestack->current_filename);
  3396.   tag_table = filestack->tag_table;
  3397.   info_file = filestack->info_file;
  3398.   info_buffer_len = filestack->info_buffer_len;
  3399.   set_search_constraints (info_file, info_buffer_len);
  3400.  
  3401.   temp = filestack;
  3402.   filestack = filestack->next;
  3403.   free ((char *)temp);
  3404. }
  3405.  
  3406. /* Swap the current info file with the bottom of the filestack */
  3407. void
  3408. swap_filestack ()
  3409. {
  3410.   char t_last_loaded_info_file[FILENAME_LEN];
  3411.   char t_current_info_file[FILENAME_LEN];
  3412.   char *t_tag_table;
  3413.   char *t_info_file;
  3414.   size_t t_info_buffer_len;
  3415.  
  3416.   if (filestack == NULL)
  3417.     {
  3418.        fprintf (stderr , "File stack is empty and can't be swapped\n");
  3419.        brians_error ();
  3420.        return;
  3421.     }
  3422.  
  3423.   strcpy (t_last_loaded_info_file, filestack->filename);
  3424.   strcpy (t_current_info_file, filestack->current_filename);
  3425.   t_tag_table = filestack->tag_table;
  3426.   t_info_file = info_file;
  3427.   t_info_buffer_len = info_buffer_len;
  3428.  
  3429.   strcpy (filestack->filename, last_loaded_info_file);
  3430.   strcpy (filestack->current_filename, current_info_file);
  3431.   filestack->tag_table = tag_table;
  3432.   filestack->info_file = info_file;
  3433.   filestack->info_buffer_len = info_buffer_len;
  3434.  
  3435.   strcpy (last_loaded_info_file, t_last_loaded_info_file);
  3436.   strcpy (current_info_file, t_current_info_file);
  3437.   tag_table = t_tag_table;
  3438.   info_file = t_info_file;
  3439.   info_buffer_len = t_info_buffer_len;
  3440. }
  3441.  
  3442. /* Now the node history stack */
  3443.  
  3444. extern NODEINFO *Info_History;
  3445.  
  3446. /* Save the current filename, nodename, and position on the history list.
  3447.    We prepend. */
  3448. int
  3449. push_node (filename, nodename, page_position, node_position)
  3450.      char *filename, *nodename;
  3451.      size_t page_position, node_position;
  3452. {
  3453.   NODEINFO *newnode = (NODEINFO *) xmalloc (sizeof (NODEINFO));
  3454.  
  3455.   newnode->next = Info_History;
  3456.  
  3457.   newnode->filename = (char *) xmalloc (strlen (filename) + 1);
  3458.   strcpy (newnode->filename, filename);
  3459.  
  3460.   newnode->nodename = (char *) xmalloc (strlen (nodename) + 1);
  3461.   strcpy (newnode->nodename, nodename);
  3462.  
  3463.   newnode->pagetop = page_position;
  3464.   newnode->nodetop = node_position;
  3465.  
  3466.   Info_History = newnode;
  3467.   return (1);
  3468. }
  3469.  
  3470. /* Pop one node from the node list, leaving the values in
  3471.    passed variables. */
  3472. int
  3473. pop_node (filename, nodename, nodetop, pagetop)
  3474.      char *filename, *nodename;
  3475.      size_t *nodetop, *pagetop;
  3476. {
  3477.   if (Info_History->next == (NODEINFO *) NULL)
  3478.     {
  3479.       display_error ("At beginning of history now!");
  3480.       return (0);
  3481.     }
  3482.   else
  3483.     {
  3484.       NODEINFO *releaser = Info_History;
  3485.  
  3486.     /* If the popped file is not the current file, then force
  3487.        the popped file to be loaded. */
  3488.       if (strcmp (Info_History->filename, last_loaded_info_file) != 0)
  3489.     last_loaded_info_file[0] = '\0';
  3490.  
  3491.       strcpy (filename, Info_History->filename);
  3492.       strcpy (nodename, Info_History->nodename);
  3493.       *pagetop = Info_History->pagetop;
  3494.       *nodetop = Info_History->nodetop;
  3495.       free (Info_History->nodename);
  3496.       free (Info_History->filename);
  3497.       Info_History = Info_History->next;
  3498.       free (releaser);
  3499.       return (1);
  3500.     }
  3501. }
  3502.  
  3503. /* Whoops, Unix doesn't have strnicmp. */
  3504.  
  3505. /* Compare at most COUNT characters from string1 to string2.  Case
  3506.    doesn't matter. */
  3507. int
  3508. strnicmp (string1, string2, count)
  3509.      const char *string1, *string2;
  3510.      size_t count;
  3511. {
  3512.   char ch1, ch2;
  3513.  
  3514.   while (count)
  3515.     {
  3516.       ch1 = *string1++;
  3517.       ch2 = *string2++;
  3518.       if (to_upper (ch1) == to_upper (ch2))
  3519.     count--;
  3520.       else
  3521.     break;
  3522.     }
  3523.   return (count);
  3524. }
  3525.  
  3526. /* Compare string1 to string2.  Case doesn't matter. */
  3527. int
  3528. stricmp (string1, string2)
  3529.      const char *string1, *string2;
  3530. {
  3531.   char ch1, ch2;
  3532.  
  3533.   while (1)
  3534.     {
  3535.       ch1 = *string1++;
  3536.       ch2 = *string2++;
  3537.  
  3538.       if (ch1 == '\0')
  3539.     return (ch2 != '\0');
  3540.  
  3541.       if ((ch2 == '\0') ||
  3542.       (to_upper (ch1) != to_upper (ch2)))
  3543.     return (1);
  3544.     }
  3545. }
  3546.  
  3547. /* Make the user type "Y" or "N". */
  3548. int 
  3549. get_y_or_n_p ()
  3550. {
  3551.   int character;
  3552.   print_string (" (Y or N)?");
  3553.   clear_eol ();
  3554.  
  3555. until_we_like_it:
  3556.  
  3557.   character = blink_cursor ();
  3558.   if (character == EOF)
  3559.     return (0);
  3560.   if (to_upper (character) == 'Y')
  3561.     {
  3562.       charout (character);
  3563.       return (1);
  3564.     }
  3565.  
  3566.   if (to_upper (character) == 'N')
  3567.     {
  3568.       charout (character);
  3569.       return (0);
  3570.     }
  3571.  
  3572.   if (character == ABORT_CHAR)
  3573.     {
  3574.       ding ();
  3575.       return (0);
  3576.     }
  3577.  
  3578.   goto until_we_like_it;
  3579. }
  3580.  
  3581. /* Move the cursor to the desired column in the window. */
  3582. void
  3583. indent_to (screen_column)
  3584.      int screen_column;
  3585. {
  3586.   int counter = screen_column - the_window.ch;
  3587.   if (counter > 0)
  3588.     {
  3589.       while (counter--)
  3590.     charout (' ');
  3591.     }
  3592.   else if (screen_column != 0)
  3593.     charout (' ');
  3594. }
  3595.  
  3596.  
  3597. /* **************************************************************** */
  3598. /*                                    */
  3599. /*            Error output/handling.                */
  3600. /*                                    */
  3601. /* **************************************************************** */
  3602.  
  3603. /* Display specific error from known file error table. */
  3604. void
  3605. file_error (file)
  3606.      char *file;
  3607. {
  3608.   extern int errno;
  3609.   extern int sys_nerr;
  3610.   extern char *sys_errlist[];
  3611.  
  3612.   if (errno < sys_nerr)
  3613.     display_error ("%s: %s", file, sys_errlist[errno]);
  3614.   else
  3615.     display_error ("%s: Unknown error %d", file, errno);
  3616. }
  3617.  
  3618. /* Display the error in the echo-area using format_string and args.
  3619.    This is a specialized interface to printf. */
  3620. #if __STDC__
  3621. void
  3622. display_error (const char *format_string,...)
  3623. {
  3624.   va_list args;
  3625.   extern int terminal_inited_p;
  3626.   char output_buffer[1024];
  3627.  
  3628.   if (totally_inhibit_errors)
  3629.     return;
  3630.  
  3631.   va_start(args, format_string);
  3632.   vsprintf (output_buffer, format_string, args);
  3633.   va_end(args);
  3634.   if (terminal_inited_p)
  3635.     {
  3636.       new_echo_area ();
  3637.       ding ();
  3638.       print_string ("%s", output_buffer);
  3639.       close_echo_area ();
  3640.     }
  3641.   else
  3642.     {
  3643.       fprintf (STDERR, "%s\n", output_buffer);
  3644.       sleep(1);
  3645.     }
  3646. }
  3647. #else /* !__STDC__ */
  3648. void
  3649. display_error (format_string, arg1, arg2)
  3650.      char *format_string;
  3651. {
  3652.   extern int terminal_inited_p;
  3653.   char output_buffer[1024];
  3654.  
  3655.   if (totally_inhibit_errors)
  3656.     return;
  3657.  
  3658.   sprintf (output_buffer, format_string, arg1, arg2);
  3659.   if (terminal_inited_p)
  3660.     {
  3661.       new_echo_area ();
  3662.       ding ();
  3663.       print_string ("%s", output_buffer);
  3664.       close_echo_area ();
  3665.     }
  3666.   else
  3667.     {
  3668.       fprintf (stderr, "%s\n", output_buffer);
  3669.     }
  3670. }
  3671. #endif /* !__STDC__ */
  3672.  
  3673. /* Tell everybody what a loser I am.  If you see this error,
  3674.    send me a bug report. */
  3675. int
  3676. brians_error ()
  3677. {
  3678.   display_error ("You are never supposed to see this error.\n");
  3679.   display_error ("Tell bfox@ai.mit.edu to fix this someday.\n");
  3680.   return (-1);
  3681. }
  3682.  
  3683. /* **************************************************************** */
  3684. /*                                    */
  3685. /*            Terminal IO, and Driver                */
  3686. /*                                    */
  3687. /* **************************************************************** */
  3688.  
  3689. /* The Unix termcap interface code. */
  3690.  
  3691. #define NO_ERROR 0
  3692. #define GENERIC_ERROR 1
  3693. #define NO_TERMINAL_DESCRIPTOR 2
  3694. #define OUT_OF_MEMORY 3
  3695. #define BAD_TERMINAL 4
  3696.  
  3697. #define FERROR(msg)    fprintf (stderr, msg); exit (GENERIC_ERROR)
  3698.  
  3699. #ifndef atarist
  3700. extern int tgetnum (), tgetflag ();
  3701. extern char *tgetstr ();
  3702. extern char *tgoto ();
  3703. #endif /* !atarist */
  3704.  
  3705. #define Certainly_enough_space 2048    /* page 3, Section 1.1, para 4 */
  3706.  
  3707. #if defined (unix)
  3708. char termcap_buffer[Certainly_enough_space];
  3709. #else /* !unix */
  3710. #define termcap_buffer NULL
  3711. #endif /* !unix */
  3712.  
  3713. /* You CANNOT remove these next four vars.  TERMCAP needs them to operate. */
  3714. char PC;
  3715. char *BC;
  3716. char *UP;
  3717.  
  3718. /* A huge array of stuff to get from termcap initialization. */
  3719.  
  3720. #define tc_int 0
  3721. #define tc_char tc_int+1
  3722. #define tc_flag tc_char+1
  3723. #define tc_last tc_flag+1
  3724.  
  3725. typedef int flag;
  3726.  
  3727. /* First, the variables which this array refers to */
  3728.  
  3729. /* Capabilities */
  3730.  
  3731. int terminal_columns;        /* {tc_int, "co" */
  3732. int terminal_rows;        /* {tc_int, "li" */
  3733. flag terminal_is_generic;    /* {tc_flag,"gn" */
  3734.  
  3735.  /* Cursor Motion */
  3736.  
  3737. char *terminal_goto;        /* {tc_char,"cm" */
  3738. char *terminal_home;        /* {tc_char,"ho" */
  3739.  
  3740. char *terminal_cursor_left;    /* {tc_char,"le" */
  3741. char *terminal_cursor_right;    /* {tc_char,"nd" */
  3742. char *terminal_cursor_up;    /* {tc_char,"up" */
  3743. char *terminal_cursor_down;    /* {tc_char,"do" */
  3744.  
  3745. /* Screen Clearing */
  3746.  
  3747. char *terminal_clearpage;    /* {tc_char,"cl" */
  3748. char *terminal_clearEOP;    /* {tc_char,"cd" */
  3749. char *terminal_clearEOL;    /* {tc_char,"ce" */
  3750.  
  3751. /* "Standout" */
  3752. char *terminal_standout_begin;    /* {tc_char,"so" */
  3753. char *terminal_standout_end;    /* {tc_char,"se" */
  3754.  
  3755. /* Reverse Video */
  3756. char *terminal_inverse_begin;    /* {tc_char,"mr" */
  3757. char *terminal_end_attributes;    /* {tc_char,"me" */
  3758.  
  3759. /* Ding! */
  3760.  
  3761. char *terminal_ear_bell;    /* {tc_char,"bl" */
  3762.  
  3763. /* Terminal Initialization */
  3764.  
  3765. char *terminal_use_begin;    /* {tc_char,"ti" */
  3766. char *terminal_use_end;        /* {tc_char,"te" */
  3767.  
  3768. /* Padding Stuff */
  3769.  
  3770. char *terminal_padding;        /* {tc_char,"pc" */
  3771.  
  3772. /* Now the whopping big array */
  3773.  
  3774. /** already defined 
  3775. typedef struct {
  3776.   char type;
  3777.   char *name;
  3778.   char *value;
  3779. } termcap_capability_struct;
  3780. **/
  3781.  
  3782. termcap_capability_struct capabilities[] = {
  3783.  
  3784. /* Capabilities */
  3785.   
  3786.   {tc_int, "co", (char *) &terminal_columns},
  3787.   {tc_int, "li", (char *) &terminal_rows},
  3788.   {tc_flag, "gn", (char *) &terminal_is_generic},
  3789.  
  3790. /* Cursor Motion */
  3791.  
  3792.   {tc_char, "cm", (char *) &terminal_goto},
  3793.   {tc_char, "ho", (char *) &terminal_home},
  3794.  
  3795.   {tc_char, "le", (char *) &terminal_cursor_left},
  3796.   {tc_char, "nd", (char *) &terminal_cursor_right},
  3797.   {tc_char, "up", (char *) &terminal_cursor_up},
  3798.   {tc_char, "do", (char *) &terminal_cursor_down},
  3799.  
  3800. /* Screen Clearing */
  3801.   
  3802.   {tc_char, "cl", (char *) &terminal_clearpage},
  3803.   {tc_char, "cd", (char *) &terminal_clearEOP},
  3804.   {tc_char, "ce", (char *) &terminal_clearEOL},
  3805.   
  3806. /* "Standout" */
  3807.   {tc_char, "so", (char *) &terminal_standout_begin},
  3808.   {tc_char, "se", (char *) &terminal_standout_end},
  3809.  
  3810. /* Reverse Video */
  3811.   {tc_char, "mr", (char *) &terminal_inverse_begin},
  3812.   {tc_char, "me", (char *) &terminal_end_attributes},
  3813.  
  3814. /* Ding! */
  3815.  
  3816.   {tc_char, "bl", (char *) &terminal_ear_bell},
  3817.   
  3818. /* Terminal Initialization */
  3819.  
  3820.   {tc_char, "ti", (char *) &terminal_use_begin},
  3821.   {tc_char, "te", (char *) &terminal_use_end},
  3822.  
  3823. /* Padding Stuff */
  3824.   
  3825.   {tc_char, "pc", (char *) &terminal_padding},
  3826.  
  3827. /* Terminate this array with a var of type tc_last */
  3828.   {tc_last, NULL, NULL}
  3829.  
  3830. };
  3831.  
  3832. int terminal_opened_p = 0;
  3833.  
  3834. int
  3835. open_terminal_io ()
  3836. {
  3837.   int error;
  3838.  
  3839.   if (terminal_opened_p)
  3840.     return (NO_ERROR);
  3841.  
  3842.   if ((error = get_terminal_info ()) != NO_ERROR)
  3843.     return (error);
  3844.  
  3845.   if ((error = get_terminal_vars (capabilities)) != NO_ERROR)
  3846.     return (error);
  3847.  
  3848.   /* Now, make sure we have the capabilites that we need. */
  3849.   if (terminal_is_generic)
  3850.     return (BAD_TERMINAL);
  3851.  
  3852.   terminal_opened_p++;
  3853.   return (NO_ERROR);
  3854. }
  3855.  
  3856.  
  3857. int
  3858. get_terminal_info ()
  3859. {
  3860.   char temp_string_buffer[256];
  3861.   int result;
  3862.  
  3863.   char *terminal_name = getenv ("TERM");
  3864.  
  3865.   if (terminal_name == NULL || *terminal_name == 0
  3866.       || (strcmp (terminal_name, "dialup") == 0))
  3867.     {
  3868.       terminal_name = temp_string_buffer;
  3869. #ifndef atarist
  3870.       printf ("\nTerminal Type:");
  3871.       fflush (stdout);
  3872.       fgets (terminal_name, 256, stdin);
  3873.       if (!(*terminal_name))
  3874.     return (NO_TERMINAL_DESCRIPTOR);
  3875. #else  /* atarist */
  3876.       *terminal_name = '\0';
  3877.       /* We have at least a default terminal descriptor on Atari */
  3878. #endif /* atarist */
  3879.     }
  3880.  
  3881. /* #define VERBOSE_GET_TERMINAL 1 */
  3882. #ifdef VERBOSE_GET_TERMINAL
  3883.  
  3884. #define buffer_limit 256
  3885. #define opsys_termcap_filename "/etc/termcap"
  3886.  
  3887.   /* We hack question mark if that is what the user typed.  All this means
  3888.      is we read /etc/termcap, and prettily print out the names of terminals
  3889.      that we find. */
  3890.  
  3891.   if (terminal_name[0] == '?' && !terminal_name[1])
  3892.     {
  3893.       FILE *termcap_file;
  3894.       if ((termcap_file = fopen (opsys_termcap_filename, "r")) != NULL)
  3895.     {
  3896.       int result;
  3897.       char line_buffer[buffer_limit];
  3898.       int terminal_count = 0;
  3899.  
  3900.       while ((readline_termcap (termcap_file, line_buffer)) != EOF)
  3901.         {
  3902.           char first_char = *line_buffer;
  3903.           if (first_char == '#' || first_char == ' '
  3904.           || first_char == '\t' || first_char == '\n')
  3905.         ;
  3906.           else
  3907.         {
  3908.           /* Print the names the pretty way. */
  3909.           printf ("\n%s", line_buffer);    /* liar */
  3910.           terminal_count++;
  3911.         }
  3912.         }
  3913.       fclose (termcap_file);
  3914.  
  3915.       if (terminal_count)
  3916.         printf ("\n%d terminals listed.\n", terminal_count);
  3917.       else
  3918.         printf ("\nNo terminals were listed.  Brian's mistake.\n");
  3919.     }
  3920.       else
  3921.     {
  3922.       fprintf (stderr,
  3923.            "\nNo such system file as %s!\nWe lose badly.\n",
  3924.            opsys_termcap_filename);
  3925.       return (NO_TERMINAL_DESCRIPTOR);
  3926.     }
  3927.       return (get_terminal_info ());
  3928.     }
  3929. #endif /* VERBOSE_GET_TERMINAL */
  3930.  
  3931.   result = tgetent (termcap_buffer, terminal_name);
  3932.  
  3933.   if (!result)
  3934.     return (NO_TERMINAL_DESCRIPTOR);
  3935.   else
  3936.     return (NO_ERROR);
  3937. }
  3938.  
  3939. #ifdef VERBOSE_GET_TERMINAL
  3940. readline_termcap (stream, buffer)
  3941.      FILE *stream;
  3942.      char *buffer;
  3943. {
  3944.   int c;
  3945.   int buffer_index = 0;
  3946.  
  3947.   while ((c = getc (stream)) != EOF && c != '\n')
  3948.     {
  3949.       if (buffer_index != buffer_limit - 1)
  3950.     buffer[buffer_index++] = c;
  3951.     }
  3952.  
  3953.   buffer[buffer_index] = 0;
  3954.  
  3955.   if (c == EOF)
  3956.     return ((buffer_index) ? 0 : EOF);
  3957.   else
  3958.     return (0);
  3959. }
  3960. #endif /* VERBOSE_GET_TERMINAL */
  3961.  
  3962. /* For each element of "from_array", read the corresponding variable's
  3963.    value into the right place. */
  3964. int
  3965. get_terminal_vars (from_array)
  3966.      termcap_capability_struct from_array[];
  3967. {
  3968.   int i;
  3969.   register termcap_capability_struct *item;
  3970.   char *buffer;
  3971.  
  3972. #if !defined (GNU_TERMCAP)
  3973.   buffer = (char *) xmalloc (sizeof (termcap_buffer) + 1);
  3974. # define buffer_space &buffer
  3975. #else
  3976. # define buffer_space 0
  3977. #endif
  3978.  
  3979.   for (i = 0; (item = &from_array[i]) && (item->type != tc_last); i++)
  3980.     {
  3981.       switch (item->type)
  3982.     {
  3983.     case tc_int:
  3984.       *((int *) (item->value)) = tgetnum (item->name);
  3985.       break;
  3986.  
  3987.     case tc_flag:
  3988.       *((int *) item->value) = tgetflag (item->name);
  3989.       break;
  3990.  
  3991.     case tc_char:
  3992.       *((char **) item->value) = tgetstr (item->name, buffer_space);
  3993.       break;
  3994.  
  3995.     default:
  3996.       FERROR ("Bad entry scanned in tc_struct[].\n \
  3997.            Ask bfox@ai.mit.edu to fix this someday.\n");
  3998.     }
  3999.     }
  4000.  
  4001.   PC = terminal_padding ? terminal_padding[0] : 0;
  4002.   BC = terminal_cursor_left;
  4003.   UP = terminal_cursor_up;
  4004.   return (NO_ERROR);
  4005. }
  4006.  
  4007. /* Return the number of rows this terminal has. */
  4008. int
  4009. get_terminal_rows ()
  4010. {
  4011.   int rows = 0;
  4012.  
  4013. #if defined (TIOCGWINSZ)
  4014.   {
  4015.     int tty;
  4016.     struct winsize size;
  4017.  
  4018.     tty = fileno (stdin);
  4019.  
  4020.     if (ioctl (tty, TIOCGWINSZ, &size) != -1)
  4021.       rows = size.ws_row;
  4022.   }
  4023. #endif /* TIOCGWINSZ */
  4024.  
  4025.   if (!rows)
  4026.     rows = tgetnum ("li");
  4027.  
  4028.   if (rows <= 0)
  4029.     rows = 24;
  4030.  
  4031.   return (rows);
  4032. }
  4033.  
  4034. /* Return the number of columns this terminal has. */
  4035. int
  4036. get_terminal_columns ()
  4037. {
  4038.   int columns = 0;
  4039.  
  4040. #if defined (TIOCGWINSZ)
  4041.   {
  4042.     int tty;
  4043.     struct winsize size;
  4044.  
  4045.     tty = fileno (stdin);
  4046.  
  4047.     if (ioctl (tty, TIOCGWINSZ, &size) != -1)
  4048.       columns = size.ws_col;
  4049.   }
  4050. #endif /* TIOCGWINSZ */
  4051.  
  4052.   if (!columns)
  4053.     columns = tgetnum ("co");
  4054.  
  4055.   if (columns <= 0)
  4056.     columns = 80;
  4057.  
  4058.   return (columns);
  4059. }
  4060.  
  4061. /* #define TERMINAL_INFO_PRINTING */
  4062. #ifdef TERMINAL_INFO_PRINTING
  4063.  
  4064. /* Scan this (already "get_terminal_vars"ed) array, printing out the
  4065.    capability name, and value for each entry.  Pretty print the value
  4066.    so that the terminal doesn't actually do anything, shitbrain. */
  4067. void
  4068. show_terminal_info (from_array)
  4069.      termcap_capability_struct from_array[];
  4070. {
  4071.   register int i;
  4072.   register termcap_capability_struct *item;
  4073.  
  4074.   for (i = 0; ((item = &from_array[i]) && ((item->type) != tc_last)); i++)
  4075.     {
  4076.  
  4077.       char *type_name;
  4078.       switch (item->type)
  4079.     {
  4080.     case tc_int:
  4081.       type_name = "int ";
  4082.       break;
  4083.     case tc_flag:
  4084.       type_name = "flag";
  4085.       break;
  4086.     case tc_char:
  4087.       type_name = "char";
  4088.       break;
  4089.     default:
  4090.       type_name = "Broken";
  4091.     }
  4092.  
  4093.       printf ("\t%s\t%s = ", type_name, item->name);
  4094.  
  4095.       switch (item->type)
  4096.     {
  4097.     case tc_int:
  4098.     case tc_flag:
  4099.       printf ("%d", *((int *) item->value));
  4100.       break;
  4101.     case tc_char:
  4102.       tc_pretty_print (*((char **) item->value));
  4103.       break;
  4104.     }
  4105.       printf ("\n");
  4106.     }
  4107. }
  4108.  
  4109. /* Print the contents of string without sending anything that isn't
  4110.    a normal printing ASCII character. */
  4111. void
  4112. tc_pretty_print (string)
  4113.      register char *string;
  4114. {
  4115.   register char c;
  4116.  
  4117.   if (0 == string)
  4118.     return;
  4119.   while (c = *string++)
  4120.     {
  4121.       if (CTRLP (c))
  4122.     {
  4123.       putchar ('^');
  4124.       c += 64;
  4125.     }
  4126.       putchar (c);
  4127.     }
  4128. }
  4129. #endif /* TERMINAL_INFO_PRINTING */
  4130.  
  4131.  
  4132. /* **************************************************************** */
  4133. /*                                    */
  4134. /*            Character IO, and driver            */
  4135. /*                                    */
  4136. /* **************************************************************** */
  4137.  
  4138. char *widest_line;
  4139. int terminal_inited_p = 0;
  4140.  
  4141. /* Start up the character io stuff. */
  4142. void
  4143. init_terminal_io ()
  4144. {
  4145.   if (!terminal_inited_p)
  4146.     {
  4147.       opsys_init_terminal ();
  4148.       terminal_rows = get_terminal_rows ();
  4149.       terminal_columns = get_terminal_columns ();
  4150.  
  4151.       widest_line = (char *) xmalloc (terminal_columns);
  4152.  
  4153.       terminal_inited_p = 1;
  4154.     }
  4155.  
  4156.   terminal_window.left = 0;
  4157.   terminal_window.top = 0;
  4158.   terminal_window.right = terminal_columns;
  4159.   terminal_window.bottom = terminal_rows;
  4160.  
  4161.   set_window (&terminal_window);
  4162.  
  4163.   terminal_window.bottom -= 2;
  4164.  
  4165.   set_window (&terminal_window);
  4166.  
  4167.   init_echo_area (the_window.left, the_window.bottom + 1,
  4168.           the_window.right, terminal_rows);
  4169.  
  4170.   /* Here is a list of things that the terminal has to be able to do. Do
  4171.      you think that this is too harsh? */
  4172.   if (!terminal_goto ||        /* We can't move the cursor. */
  4173.       !terminal_rows)        /* We don't how many lines it has. */
  4174.     {
  4175.       fprintf (stderr,
  4176.            "Your terminal is not clever enough to run info. Sorry.\n");
  4177.        exit (1);
  4178.     }
  4179. }
  4180.  
  4181. /* Ring the terminal bell. */
  4182. void
  4183. ding ()
  4184. {
  4185.   extern char *terminal_ear_bell;
  4186.  
  4187.   if (terminal_ear_bell)
  4188.     do_term (terminal_ear_bell);
  4189.   else
  4190.     putchar (CTRL ('G'));
  4191.  
  4192.   fflush (stdout);
  4193. }
  4194.  
  4195. int untyi_char = 0;
  4196. int inhibit_output = 0;
  4197.  
  4198. /* Return a character from stdin, or the last unread character
  4199.    if there is one available. */
  4200. int
  4201. blink_cursor ()
  4202. {
  4203.   int character;
  4204.  
  4205.   fflush (stdout);
  4206.   if (untyi_char)
  4207.     {
  4208.       character = untyi_char;
  4209.       untyi_char = 0;
  4210.     }
  4211.   else
  4212.     do { character = getc (stdin); } while (character == -1 && errno == EINTR);
  4213.  
  4214.   return (character);
  4215. }
  4216.  
  4217. /* Display single character on the terminal screen.  If the
  4218.    character would run off the right hand edge of the screen,
  4219.    advance the cursor to the next line. */
  4220. void
  4221. charout (character)
  4222.      int character;
  4223. {
  4224.   if (inhibit_output)
  4225.     return;
  4226.  
  4227.   /* This character may need special treatment if it is
  4228.      a control character. */
  4229.   if (CTRL_P (character))
  4230.     {
  4231.       switch (character)
  4232.     {
  4233.     case NEWLINE:
  4234.     case RETURN:
  4235.       print_cr ();
  4236.       break;
  4237.  
  4238.     case TAB:
  4239.       print_tab ();
  4240.       break;
  4241.  
  4242.     default:
  4243.       charout ('^');
  4244.       charout (UNCTRL (character));
  4245.     }
  4246.     }
  4247.   else
  4248.     {
  4249.       putchar (character);
  4250.       advance (1);
  4251.     }
  4252. }
  4253.  
  4254. /* Move the cursor AMOUNT character positions. */
  4255. void
  4256. advance (amount)
  4257.      int amount;
  4258. {
  4259.   int old_window_cv = the_window.cv;
  4260.  
  4261.   while (amount-- > 0)
  4262.     {
  4263.       the_window.ch++;
  4264.       if (the_window.ch >= the_window.right)
  4265.     {
  4266.       the_window.ch = (the_window.ch - the_window.right) + the_window.left;
  4267.       the_window.cv++;
  4268.  
  4269.       if (the_window.cv >= the_window.bottom)
  4270.         the_window.cv = the_window.top;
  4271.     }
  4272.     }
  4273.  
  4274.   if (the_window.cv != old_window_cv)
  4275.     goto_xy (the_window.ch, the_window.cv);
  4276. }
  4277.  
  4278. /* Print STRING and args using charout */
  4279. #if __STDC__
  4280. void
  4281. print_string (const char *string, ...)
  4282. {
  4283.   va_list args;
  4284.   int character;
  4285.   char buffer[2048];
  4286.   int idx = 0;
  4287.  
  4288.   va_start(args, string);
  4289.   vsprintf (buffer, string, args);
  4290.   va_end(args);
  4291.  
  4292.   while (character = buffer[idx++])
  4293.     charout (character);
  4294.  
  4295.   fflush (stdout);
  4296. }
  4297. #else /* !__STDC__ */
  4298. void
  4299. print_string (string, a1, a2, a3, a4, a5)
  4300.      char *string;
  4301. {
  4302.   int character;
  4303.   char buffer[2048];
  4304.   int idx = 0;
  4305.  
  4306.   sprintf (buffer, string, a1, a2, a3, a4, a5);
  4307.  
  4308.   while (character = buffer[idx++])
  4309.     charout (character);
  4310.  
  4311.   fflush (stdout);
  4312. }
  4313. #endif /* !__STDC__ */
  4314.  
  4315. /* Display a carriage return.
  4316.    Clears to the end of the line first. */
  4317. void
  4318. print_cr ()
  4319. {
  4320.   extern int typing_out;
  4321.   clear_eol ();
  4322.  
  4323.   if (typing_out)
  4324.     {                /* Do the "MORE" stuff. */
  4325.       int response;
  4326.  
  4327.       if (the_window.cv + 2 == the_window.bottom)
  4328.     {
  4329.       goto_xy (the_window.left, the_window.cv + 1);
  4330.       clear_eol ();
  4331.       print_string ("[More]");
  4332.       response = blink_cursor ();
  4333.       if (response != SPACE)
  4334.         {
  4335.           untyi_char = response;
  4336.           inhibit_output = 1;
  4337.           return;
  4338.         }
  4339.       else
  4340.         {
  4341.           goto_xy (the_window.left, the_window.cv);
  4342.           clear_eol ();
  4343.           goto_xy (the_window.left, the_window.top);
  4344.           return;
  4345.         }
  4346.     }
  4347.     }
  4348.   advance (the_window.right - the_window.ch);
  4349. }
  4350.  
  4351. /* Move the cursor to the next tab stop, blanking the intervening
  4352.    spaces along the way. */
  4353. void
  4354. print_tab ()
  4355. {
  4356.   int hpos, width, destination;
  4357.  
  4358.   hpos = the_window.ch - the_window.left;
  4359.   width = ((hpos + 8) & 0xf8) - hpos;
  4360.  
  4361.   destination = hpos + width + the_window.left;
  4362.  
  4363.   if (destination >= the_window.right)
  4364.     destination -= the_window.right;
  4365.  
  4366.   while (the_window.ch != destination)
  4367.     charout (SPACE);
  4368. }
  4369.  
  4370. int
  4371. display_width (character, hpos)
  4372.      int character, hpos;
  4373. {
  4374.   int width = 1;
  4375.  
  4376.   if (CTRL_P (character))
  4377.     {
  4378.       switch (character)
  4379.     {
  4380.     case RETURN:
  4381.     case NEWLINE:
  4382.       width = the_window.right - hpos;
  4383.       break;
  4384.     case TAB:
  4385.       width = ((hpos + 8) & 0xf8) - hpos;
  4386.       break;
  4387.     default:
  4388.       width = 2;
  4389.     }
  4390.     }
  4391.   return (width);
  4392. }
  4393.  
  4394. /* Like GOTO_XY, but do it right away. */
  4395. void
  4396. I_goto_xy (xpos, ypos)
  4397.      int xpos, ypos;
  4398. {
  4399.   goto_xy (xpos, ypos);
  4400.   fflush (stdout);
  4401. }
  4402.  
  4403. void
  4404. /* Move the cursor, (and cursor variables) to xpos, ypos. */
  4405. goto_xy (xpos, ypos)
  4406.      int xpos, ypos;
  4407. {
  4408.   the_window.ch = xpos;
  4409.   the_window.cv = ypos;
  4410.   opsys_goto_pos (xpos, ypos);
  4411. }
  4412.  
  4413. /* Clear the screen, leaving ch and cv at the top of the window. */
  4414. void
  4415. clear_screen ()
  4416. {
  4417.   goto_xy (the_window.left, the_window.top);
  4418.   clear_eop_slowly ();
  4419. }
  4420.  
  4421. void
  4422. clear_eop_slowly ()
  4423. {
  4424.   int temp_ch = the_window.ch;
  4425.   int temp_cv = the_window.cv;
  4426.  
  4427.   clear_eol ();
  4428.  
  4429.   while (++the_window.cv < the_window.bottom)
  4430.     {
  4431.       goto_xy (the_window.left, the_window.cv);
  4432.       clear_eol ();
  4433.     }
  4434.   goto_xy (temp_ch, temp_cv);
  4435. }
  4436.  
  4437. /* Clear from current cursor position to end of page. */
  4438. void
  4439. clear_eop ()
  4440. {
  4441.   if (terminal_clearEOP)
  4442.     do_term (terminal_clearEOP);
  4443.   else
  4444.     clear_eop_slowly ();
  4445. }
  4446.  
  4447. /* Clear from current cursor position to end of screen line */
  4448. void
  4449. clear_eol ()
  4450. {
  4451.   int temp_ch = the_window.ch;
  4452.  
  4453.   if (terminal_clearEOL)
  4454.     do_term (terminal_clearEOL);
  4455.   else
  4456.     {
  4457.       char *line = widest_line;
  4458.       int i;
  4459.  
  4460.       for (i = 0; i < the_window.right - the_window.ch; i++)
  4461.     line[i] = ' ';
  4462.       line[i] = '\0';
  4463.  
  4464.       printf ("%s", line);
  4465.     }
  4466.   goto_xy (temp_ch, the_window.cv);
  4467. }
  4468.  
  4469. /* Call FUNCTION with WINDOW active.  You can pass upto 5 args to the
  4470.    function.  This returns whatever FUNCTION returns. */
  4471. #if __STDC__
  4472. /* this is used only with display_page() function, so we will not bother
  4473.    with full generality stuff */
  4474. int
  4475. with_output_to_window (WINDOW *window, Function *function)
  4476. {
  4477.   int result;
  4478.   push_window ();
  4479.   set_window (window);
  4480.   result = (*function) ();
  4481.   pop_window ();
  4482.   return (result);
  4483. }
  4484. #else /* !__STDC__ */
  4485. int
  4486. with_output_to_window (window, function, arg1, arg2, arg3, arg4, arg5)
  4487.      WINDOW *window;
  4488.      Function *function;
  4489. {
  4490.   int result;
  4491.  
  4492.   push_window ();
  4493.   set_window (window);
  4494.   result = (*function) (arg1, arg2, arg3, arg4, arg5);
  4495.   pop_window ();
  4496.   return (result);
  4497. }
  4498. #endif /* !__STDC__ */
  4499.  
  4500. /* Given a pointer to a window data structure, make that
  4501.    the current window. */
  4502. void
  4503. set_window (window)
  4504.      WINDOW *window;
  4505. {
  4506.   bcopy (window, &the_window, sizeof (WINDOW));
  4507. }
  4508.  
  4509. /* Save the current window on the window stack. */
  4510. void
  4511. push_window ()
  4512. {
  4513.   WINDOW_LIST *new_window = (WINDOW_LIST *) xmalloc (sizeof (WINDOW_LIST));
  4514.  
  4515.   new_window->next_window = window_stack;
  4516.   window_stack = new_window;
  4517.   new_window->ch = the_window.ch;
  4518.   new_window->cv = the_window.cv;
  4519.   new_window->top = the_window.top;
  4520.   new_window->bottom = the_window.bottom;
  4521.   new_window->left = the_window.left;
  4522.   new_window->right = the_window.right;
  4523. }
  4524.  
  4525. /* Pop the top of the window_stack into the_window. */
  4526. void
  4527. pop_window ()
  4528. {
  4529.   set_window ((WINDOW *)window_stack);
  4530.  
  4531.   if (window_stack->next_window)
  4532.     {
  4533.       WINDOW_LIST *thing_to_free = window_stack;
  4534.       window_stack = window_stack->next_window;
  4535.       free (thing_to_free);
  4536.     }
  4537.  
  4538.   goto_xy (the_window.ch, the_window.cv);
  4539. }
  4540.  
  4541. /* **************************************************************** */
  4542. /*                                    */
  4543. /*            "Opsys" functions.                */
  4544. /*                                    */
  4545. /* **************************************************************** */
  4546.  
  4547. /* The lowlevel terminal/file interface.  Nothing ever really gets
  4548.    low level when you're writing in C, though.
  4549.  
  4550.    This file contains all of the "opsys" labels.  You have to make
  4551.    a different one if you want GNU Info to run on machines that don't
  4552.    have Unix.  */
  4553.  
  4554. extern char *terminal_use_begin, *terminal_use_end, *terminal_goto;
  4555.  
  4556. #if defined (TIOCGETC)
  4557. struct tchars original_tchars;
  4558. #endif
  4559.  
  4560. #if defined (TIOCGLTC)
  4561. struct ltchars original_ltchars;
  4562. #endif
  4563.  
  4564. #if defined (USG)
  4565. struct termio original_termio, ttybuff;
  4566. #else
  4567. int original_tty_flags = 0;
  4568. int original_lmode;
  4569. struct sgttyb ttybuff;
  4570. #endif /* !USG */
  4571.  
  4572. /* Yes, that's right, do things that the machine needs to get
  4573.    the terminal into a usable mode. */
  4574. void
  4575. opsys_init_terminal ()
  4576. {
  4577.   int tty = fileno (stdin);
  4578.  
  4579. #if defined (USG)
  4580.   ioctl (tty, TCGETA, &original_termio);
  4581.   ioctl (tty, TCGETA, &ttybuff);
  4582.   ttybuff.c_iflag &= (~ISTRIP & ~INLCR & ~IGNCR & ~ICRNL &~IXON);
  4583.   ttybuff.c_oflag &= (~ONLCR & ~OCRNL);
  4584.   ttybuff.c_lflag &= (~ICANON & ~ECHO);
  4585.  
  4586.   ttybuff.c_cc[VMIN] = 1;
  4587.   ttybuff.c_cc[VTIME] = 0;
  4588.  
  4589.   if (ttybuff.c_cc[VINTR] = DELETE)
  4590.     ttybuff.c_cc[VINTR] = -1;
  4591.  
  4592.   if (ttybuff.c_cc[VQUIT] = DELETE)
  4593.     ttybuff.c_cc[VQUIT] = -1;
  4594.  
  4595.   ioctl (tty, TCSETA, &ttybuff);
  4596. #else /* !USG */
  4597.  
  4598.   ioctl (tty, TIOCGETP, &ttybuff);
  4599.  
  4600.   if (!original_tty_flags)
  4601.     original_tty_flags = ttybuff.sg_flags;
  4602.  
  4603.   /* Make this terminal pass 8 bits around while we are using it. */
  4604. #ifdef PASS8
  4605.   ttybuff.sg_flags |= PASS8;
  4606. #endif
  4607.  
  4608. #if defined (TIOCLGET) && defined (LPASS8)
  4609.   {
  4610.     int flags;
  4611.     ioctl (tty, TIOCLGET, &flags);
  4612.     original_lmode = flags;
  4613.     flags |= LPASS8;
  4614.     ioctl (tty, TIOCLSET, &flags);
  4615.   }
  4616. #endif
  4617.  
  4618. #ifdef TIOCGETC
  4619.   {
  4620.     struct tchars temp;
  4621.  
  4622.     ioctl (tty, TIOCGETC, &original_tchars);
  4623.     bcopy (&original_tchars, &temp, sizeof (struct tchars));
  4624.  
  4625.     temp.t_startc = temp.t_stopc = -1;
  4626.  
  4627.     /* If the quit character conflicts with one of our commands, then
  4628.        make it go away. */
  4629.     if (temp.t_intrc == DELETE)
  4630.       temp.t_intrc = -1;
  4631.  
  4632.     if (temp.t_quitc == DELETE)
  4633.       temp.t_quitc = -1;
  4634.  
  4635.     ioctl (tty, TIOCSETC, &temp);
  4636.   }
  4637. #endif /* TIOCGETC */
  4638.  
  4639. #ifdef TIOCGLTC
  4640.   {
  4641.     struct ltchars temp;
  4642.  
  4643.     ioctl (tty, TIOCGLTC, &original_ltchars);
  4644.     bcopy (&original_ltchars, &temp, sizeof (struct ltchars));
  4645.  
  4646.     /* Make the interrupt keys go away.  Just enough to make people happy. */
  4647.     temp.t_lnextc = -1;        /* C-v */
  4648.  
  4649.     ioctl (tty, TIOCSLTC, &temp);
  4650.   }
  4651. #endif /* TIOCGLTC */
  4652.  
  4653.   ttybuff.sg_flags &= ~ECHO;
  4654.   ttybuff.sg_flags |= CBREAK;
  4655.   ioctl (tty, TIOCSETN, &ttybuff);
  4656. #endif /* !USG */
  4657.  
  4658.   open_terminal_io ();
  4659.   do_term (terminal_use_begin);
  4660. }
  4661.  
  4662. /* Fix the terminal that I broke. */
  4663. void
  4664. restore_io ()
  4665. {
  4666.   int tty = fileno (stdin);
  4667.  
  4668. #if defined (USG)
  4669.   ioctl (tty, TCSETA, &original_termio);
  4670. #else
  4671.   ioctl (tty, TIOCGETP, &ttybuff);
  4672.   ttybuff.sg_flags = original_tty_flags;
  4673.   ioctl (tty, TIOCSETN, &ttybuff);
  4674.  
  4675. #ifdef TIOCGETC
  4676.   ioctl (tty, TIOCSETC, &original_tchars);
  4677. #endif /* TIOCGETC */
  4678.  
  4679. #ifdef TIOCGLTC
  4680.   ioctl (tty, TIOCSLTC, &original_ltchars);
  4681. #endif /* TIOCGLTC */
  4682.  
  4683. #if defined (TIOCLGET) && defined (LPASS8)
  4684.   ioctl (tty, TIOCLSET, &original_lmode);
  4685. #endif
  4686.  
  4687. #endif /* !USG */
  4688.   do_term (terminal_use_end);
  4689. }
  4690.  
  4691. void
  4692. opsys_goto_pos (xpos, ypos)
  4693.      int xpos, ypos;
  4694. {
  4695.   do_term (tgoto (terminal_goto, xpos, ypos));
  4696. }
  4697.  
  4698. /* return type of this function required by tputs */
  4699. int
  4700. character_output_function (character)
  4701.      char character;
  4702. {
  4703.   return(putchar (character));
  4704. }
  4705.  
  4706. /* Generic interface to tputs. */
  4707. void
  4708. do_term (command)
  4709.      char *command;
  4710. {
  4711.   /* Send command to the terminal, with appropriate padding. */
  4712.   if (command)
  4713.     tputs (command, 1, character_output_function);
  4714. }
  4715.  
  4716. /* Filename manipulators, and the like. */
  4717. char local_temp_filename[FILENAME_LEN];
  4718.  
  4719. #ifndef atarist
  4720. const char *info_suffixes[] = {
  4721.   "",
  4722.   ".info",
  4723.   "-info",
  4724.   (const char *)NULL
  4725. };
  4726.  
  4727. /* A structure which associates the argument passed into a function with
  4728.    the result from that function. */
  4729. typedef struct {
  4730.   char *called;
  4731.   char *result;
  4732. } CALLED_RESULTS;
  4733.  
  4734. /* An array of remembered arguments and results. */
  4735. static CALLED_RESULTS **opsys_callers_and_results = (CALLED_RESULTS **)NULL;
  4736. static int next_caller_and_result = 0;
  4737. static int callers_and_results_size = 0;
  4738.  
  4739. /* Find the result for having already called opsys_filename () with CALLER. */
  4740. char *
  4741. find_opsys_filename_result (caller)
  4742.      char *caller;
  4743. {
  4744.   if (caller && opsys_callers_and_results)
  4745.     {
  4746.       register int i;
  4747.       for (i = 0; opsys_callers_and_results[i]; i++)
  4748.     {
  4749.       if (strcmp (opsys_callers_and_results[i]->called, caller) == 0)
  4750.         return (opsys_callers_and_results[i]->result);
  4751.     }
  4752.     }
  4753.   return (char *)NULL;;
  4754. }
  4755.   
  4756. /* Add an argument and result to our list. */
  4757. void
  4758. add_caller_and_result (caller, result)
  4759.      char *caller, *result;
  4760. {
  4761.   CALLED_RESULTS *new;
  4762.  
  4763.   if (next_caller_and_result + 2 > callers_and_results_size)
  4764.     {
  4765.       int alloc_size;
  4766.       callers_and_results_size += 10;
  4767.  
  4768.       alloc_size = callers_and_results_size * sizeof (CALLED_RESULTS *);
  4769.  
  4770.       opsys_callers_and_results = (CALLED_RESULTS **)
  4771.     xrealloc (opsys_callers_and_results, alloc_size);
  4772.     }
  4773.  
  4774.   new = (CALLED_RESULTS *)xmalloc (sizeof (CALLED_RESULTS));
  4775.   new->called = savestring (caller);
  4776.   new->result = result ? savestring (result) : (char *)NULL;
  4777.  
  4778.   opsys_callers_and_results[next_caller_and_result++] = new;
  4779.   opsys_callers_and_results[next_caller_and_result] = (CALLED_RESULTS *)NULL;
  4780. }
  4781.  
  4782. /* Expand the filename in partial to make a real name for
  4783.    this operating system.  This looks in INFO_PATHS in order to
  4784.    find the correct file.  If it can't find the file, it just
  4785.    returns the path as you gave it. */
  4786. char *
  4787. opsys_filename (partial)
  4788.      char *partial;
  4789. {
  4790.   int initial_character;
  4791.   char *my_infopath;
  4792.  
  4793.   if (partial && (initial_character = *partial))
  4794.     {
  4795.       char *possible_result;
  4796.  
  4797.       if (initial_character == '/')
  4798.     return (partial);
  4799.  
  4800.       possible_result = find_opsys_filename_result (partial);
  4801.       if (possible_result)
  4802.     return (possible_result);
  4803.  
  4804.       if (initial_character == '~')
  4805.     {
  4806.       if (partial[1] == '/')
  4807.         {
  4808.           /* Return the concatenation of HOME and the rest
  4809.          of the string. */
  4810.           strcpy (local_temp_filename, getenv ("HOME"));
  4811.           strcat (local_temp_filename, &partial[2]);
  4812.           return (local_temp_filename);
  4813.         }
  4814.       else
  4815.         {
  4816.           struct passwd *user_entry;
  4817.           int i, c;
  4818.           char username[257];
  4819.  
  4820.           for (i = 1; c = partial[i]; i++)
  4821.         {
  4822.           if (c == '/')
  4823.             break;
  4824.           else
  4825.             username[i - 1] = c;
  4826.         }
  4827.           username[i - 1] = '\0';
  4828.  
  4829.           if (!(user_entry = getpwnam (username)))
  4830.         {
  4831.           display_error ("Not a registered user!");
  4832.           return (partial);
  4833.         }
  4834.           strcpy (local_temp_filename, user_entry->pw_dir);
  4835.           strcat (local_temp_filename, &partial[i]);
  4836.           return (local_temp_filename);
  4837.         }
  4838.     }
  4839.  
  4840.       if (initial_character == '.' &&
  4841.       (partial[1] == '/' || (partial[1] == '.' && partial[2] == '/')))
  4842.     {
  4843. #if defined (USG)
  4844.       if (!getcwd (local_temp_filename, FILENAME_LEN))
  4845. #else
  4846.       if (!getwd (local_temp_filename))
  4847. #endif
  4848.         {
  4849.           display_error (local_temp_filename);
  4850.           return (partial);
  4851.         }
  4852.  
  4853.       strcat (local_temp_filename, "/");
  4854.       strcat (local_temp_filename, partial);
  4855.       return (local_temp_filename);
  4856.     }
  4857.  
  4858.       /* If the current info file has a directory, then search that directory
  4859.      first. */
  4860.       {
  4861.     register char *ptr, *file;
  4862.  
  4863.     file = find_opsys_filename_result (current_info_file);
  4864.  
  4865.     if (file && (ptr = (char *)rindex (file, '/')) != (char *)NULL)
  4866.       {
  4867.         register int len = (ptr - file);
  4868.  
  4869.         my_infopath = (char *)
  4870.           xmalloc (2 + strlen (file) + strlen (infopath));
  4871.  
  4872.         strncpy (my_infopath, file, len);
  4873.         sprintf (my_infopath + len, ":%s", infopath);
  4874.       }
  4875.     else
  4876.       my_infopath = savestring (infopath);
  4877.       }
  4878.  
  4879.       if (opsys_info_file_in_path (partial, my_infopath, local_temp_filename))
  4880.     {
  4881.       free (my_infopath);
  4882.       add_caller_and_result (partial, local_temp_filename);
  4883.       return (local_temp_filename);
  4884.     }
  4885.  
  4886.       free (my_infopath);
  4887.     }
  4888.   return (partial);
  4889. }
  4890.  
  4891. #if !defined (S_ISREG) && defined (S_IFREG)
  4892. #  define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
  4893. #endif /* !S_ISREG && S_IFREG */
  4894.  
  4895. #if !defined (S_ISDIR) && defined (S_IFDIR)
  4896. #  define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
  4897. #endif /* !S_ISDIR && S_IFDIR */
  4898.  
  4899. /* Scan the list of directories in PATH looking for FILENAME.
  4900.    If we find one that is a regular file, stuff the answer into
  4901.    RETURN_STRING.  Return non-zero if a file was found, zero otherwise. */
  4902. int
  4903. opsys_info_file_in_path (filename, path, return_string)
  4904.      char *filename, *path;
  4905.      char *return_string;
  4906. {
  4907.   struct stat finfo;
  4908.   char *temp_dirname, *extract_colon_unit ();
  4909.   char *test_file = return_string;
  4910.   int statable, dirname_index = 0;
  4911.  
  4912.   while (temp_dirname = extract_colon_unit (path, &dirname_index))
  4913.     {
  4914.       register int i, pre_suffix_length;
  4915.  
  4916.       strcpy (test_file, temp_dirname);
  4917.       if (temp_dirname[(strlen (temp_dirname)) - 1] != '/')
  4918.     strcat (test_file, "/");
  4919.       strcat (test_file, filename);
  4920.  
  4921.       pre_suffix_length = strlen (test_file);
  4922.  
  4923.       free (temp_dirname);
  4924.  
  4925.       for (i = 0; info_suffixes[i]; i++)
  4926.     {
  4927.       strcpy (test_file + pre_suffix_length, info_suffixes[i]);
  4928.  
  4929.       statable = (stat (test_file, &finfo) == 0);
  4930.  
  4931.       /* If we have found a regular file, then use that.  Else, if we
  4932.          have found a directory, look in that directory for this file. */
  4933.       if (statable)
  4934.         {
  4935.           if (S_ISREG (finfo.st_mode))
  4936.         return (1);
  4937.           else if (S_ISDIR (finfo.st_mode))
  4938.         {
  4939.           char *newpath = savestring (test_file);
  4940.           char *filename_only = (char *)rindex (filename, '/');
  4941.  
  4942.           if (opsys_info_file_in_path
  4943.               (filename_only ? filename_only : filename,
  4944.                newpath, return_string))
  4945.             {
  4946.               free (newpath);
  4947.               return (1);
  4948.             }
  4949.         }
  4950.         }
  4951.     }
  4952.     }
  4953.   return (0);
  4954. }
  4955.  
  4956. /* Given a string containing units of information separated by colons,
  4957.    return the next one pointed to by IDX, or NULL if there are no more.
  4958.    Advance IDX to the character after the colon. */
  4959. char *
  4960. extract_colon_unit (string, idx)
  4961.      char *string;
  4962.      int *idx;
  4963. {
  4964.   register int i, start;
  4965.  
  4966.   i = start = *idx;
  4967.   if ((i >= strlen (string)) || !string)
  4968.     return ((char *) NULL);
  4969.  
  4970.   while (string[i] && string[i] != ':')
  4971.     i++;
  4972.   if (i == start)
  4973.     {
  4974.       return ((char *) NULL);
  4975.     }
  4976.   else
  4977.     {
  4978.       char *value = (char *) xmalloc (1 + (i - start));
  4979.       strncpy (value, &string[start], (i - start));
  4980.       value[i - start] = '\0';
  4981.       if (string[i])
  4982.     ++i;
  4983.       *idx = i;
  4984.       return (value);
  4985.     }
  4986. }
  4987.  
  4988. #else /* atarist */
  4989.  
  4990. /* findfile() always tries first its argument as given */
  4991. const char *info_suffixes[] = {
  4992.   "inf",
  4993.   (const char *)NULL
  4994.   };
  4995.  
  4996. /* Expand the filename in partial to make a real name for
  4997.    this operating system.  This looks in INFO_PATHS in order to
  4998.    find the correct file.  If it can't find the file, it just
  4999.    returns the path as you gave it. */
  5000. /* Atari modification - since filename we are searching for
  5001.    can be in a form xxxxx.info-<num> we will drop from the
  5002.    name this "info" substring, no matter what. This is the form
  5003.    in which filenames will be generated on Atari by makeinfo,
  5004.    although full form is kept in an indirect tag table to
  5005.    maintain a compatibility with Un*x */
  5006. char *
  5007. opsys_filename (partial)
  5008.      char *partial;
  5009. {
  5010.    extern char *findfile (char *fnam, char *path, const char **ext);
  5011.    char  *fname, *ptr;
  5012.  
  5013.    if ((NULL != (ptr = rindex (partial, '.'))) &&
  5014.        (0 == strncmp ("info-", ++ptr, sizeof("info-") - 1)))
  5015.      /* A potential bug - there is no guarantee of a copy order */
  5016.      strcpy (ptr, ptr + sizeof("info") - 1);
  5017.  
  5018.    if (NULL == (fname = findfile (partial, infopath, &info_suffixes[0])))
  5019.      fname = partial;
  5020.    /* standarize the result */
  5021.    dos2unx (fname, fname);
  5022.    return fname;
  5023. }
  5024. #endif /* atarist */
  5025.  
  5026. /* **************************************************************** */
  5027. /*                                    */
  5028. /*            The echo area.                    */
  5029. /*                                    */
  5030. /* **************************************************************** */
  5031.  
  5032. WINDOW echo_area = {0, 0, 0, 0, 0, 0};
  5033. int echo_area_open_p = 0;
  5034. char modeline[256];
  5035. WINDOW modeline_window = {0, 0, 0, 0, 0, 0};
  5036.  
  5037. /* Define the location of the echo area. Also inits the
  5038.    modeline as well. */
  5039. void
  5040. init_echo_area (left, top, right, bottom)
  5041.      int left, top, right, bottom;
  5042. {
  5043.   echo_area.left = modeline_window.left = left;
  5044.   echo_area.top = top;
  5045.   modeline_window.top = top - 1;
  5046.   echo_area.right = modeline_window.right = right;
  5047.   echo_area.bottom = bottom;
  5048.   modeline_window.bottom = modeline_window.top;
  5049. }
  5050.  
  5051. /* Make the echo_area_window be the current window, and only allow
  5052.    output in there.  Clear the window to start. */
  5053. void
  5054. new_echo_area ()
  5055. {
  5056.   if (!echo_area_open_p)
  5057.     {
  5058.       push_window ();
  5059.       set_window (&echo_area);
  5060.       echo_area_open_p = 1;
  5061.     }
  5062.   goto_xy (the_window.left, the_window.top);
  5063.   clear_eop ();
  5064. }
  5065.  
  5066. /* Return output to the previous window. */
  5067. void
  5068. close_echo_area ()
  5069. {
  5070.   if (!echo_area_open_p)
  5071.     return;
  5072.  
  5073.   pop_window ();
  5074.   echo_area_open_p = 0;
  5075. }
  5076.  
  5077. /* Clear the contents of the echo area. */
  5078. void
  5079. clear_echo_area ()
  5080. {
  5081.   new_echo_area ();
  5082.   close_echo_area ();
  5083. }
  5084.  
  5085. /* Create and display the modeline. */
  5086. void
  5087. make_modeline ()
  5088. {
  5089.   int width = modeline_window.right - modeline_window.left;
  5090.   char textual_position[6];
  5091.  
  5092.   if (pagetop == nodetop)
  5093.     {
  5094.       if (pagebot == nodebot)
  5095.     sprintf (textual_position, "All");
  5096.       else
  5097.     sprintf (textual_position, "Top");
  5098.     }
  5099.   else
  5100.     {
  5101.       if (pagebot == nodebot)
  5102.     sprintf (textual_position, "Bot");
  5103.       else
  5104.     sprintf (textual_position, "%2lu%%",
  5105.          100 * (pagetop - nodetop) / (nodebot - nodetop));
  5106.     }
  5107.     
  5108.   sprintf (modeline, "Info: (%s)%s, %d lines ---%s",
  5109.        current_info_file, current_info_node, nodelines, textual_position);
  5110.  
  5111.   if (strnicmp
  5112.       (opsys_filename (current_info_file), last_loaded_info_file,
  5113.        strlen (last_loaded_info_file)) != 0)
  5114.     {
  5115.       sprintf (&modeline[strlen (modeline)], ", Subfile: %s",
  5116.            last_loaded_info_file);
  5117.     }
  5118.  
  5119.   if (strlen (modeline) < width)
  5120.     {
  5121.       int idx = strlen (modeline);
  5122.       while (idx != width)
  5123.     modeline[idx++] = '-';
  5124.       modeline[idx] = '\0';
  5125.     }
  5126.  
  5127.   if (strlen (modeline) > width)
  5128.     modeline[width] = '\0';
  5129.   push_window ();
  5130.   set_window (&modeline_window);
  5131.   goto_xy (the_window.left, the_window.top);
  5132.  
  5133.   if (terminal_inverse_begin) {
  5134.     do_term (terminal_inverse_begin);
  5135.     print_string ("%s", modeline);
  5136.     do_term (terminal_end_attributes);
  5137.   }  
  5138.   else if (terminal_standout_begin) {
  5139.     do_term (terminal_standout_begin);
  5140.     print_string ("%s", modeline);
  5141.     do_term (terminal_standout_end);
  5142.   }
  5143.   else
  5144.     print_string ("%s", modeline);
  5145.  
  5146.   pop_window ();
  5147. }
  5148.  
  5149. int typing_out = 0;
  5150.  
  5151. /* Prepare to do output to the typeout window.  If the
  5152.    typeout window is already open, ignore this clown. */
  5153. void
  5154. open_typeout ()
  5155. {
  5156.   if (typing_out)
  5157.     return;
  5158.  
  5159.   push_window ();
  5160.   set_window (&terminal_window);
  5161.   goto_xy (the_window.ch, the_window.cv);
  5162.   typing_out = window_bashed = 1;
  5163. }
  5164.  
  5165. /* Close the currently open typeout window. */
  5166. void
  5167. close_typeout ()
  5168. {
  5169.   if (inhibit_output)
  5170.     inhibit_output = 0;
  5171.   else
  5172.     {
  5173.       do { untyi_char = getc (stdin); }
  5174.         while (untyi_char == -1 && errno == EINTR);
  5175.  
  5176.       if (untyi_char == SPACE)
  5177.     untyi_char = 0;
  5178.     }
  5179.   pop_window ();
  5180.   typing_out = 0;
  5181. }
  5182.  
  5183. void *
  5184. xrealloc (pointer, bytes)
  5185.      void *pointer;
  5186.      size_t bytes;
  5187. {
  5188.   void *temp;
  5189.  
  5190.   if (pointer == (char *)NULL)
  5191.     temp = (void *) xmalloc (bytes);
  5192.   else
  5193.     temp = (void *) realloc (pointer, bytes);
  5194.  
  5195.   if (temp == (void *)NULL)
  5196.     {
  5197.       fprintf (stderr, "Virtual memory exhausted\n");
  5198.       restore_io ();
  5199.       exit (2);
  5200.     }
  5201.   return (temp);
  5202. }
  5203.  
  5204. void *
  5205. xmalloc (bytes)
  5206.      size_t bytes;
  5207. {
  5208.   void *temp = (void *) malloc (bytes);
  5209.  
  5210.   if (temp == (void *) NULL)
  5211.     {
  5212.       fprintf (stderr, "Virtual memory exhausted\n");
  5213.       restore_io ();
  5214.       exit (2);
  5215.     }
  5216.   return (temp);
  5217. }
  5218.